diff --git a/404.html b/404.html --- a/404.html +++ b/404.html @@ -4,7 +4,7 @@ HappyView - + diff --git a/index.html b/index.html --- a/index.html +++ b/index.html @@ -4,7 +4,7 @@ HappyView - + diff --git a/assets/js/920ba7ef.9eb561b1.js b/assets/js/920ba7ef.9eb561b1.js new file mode 100644 --- /dev/null +++ b/assets/js/920ba7ef.9eb561b1.js @@ -0,0 +1,1 @@ +"use strict";(self.webpackChunkhappyview_docs=self.webpackChunkhappyview_docs||[]).push([[5953],{1189(e,r,n){n.r(r),n.d(r,{assets:()=>t,contentTitle:()=>a,default:()=>o,frontMatter:()=>i,metadata:()=>s,toc:()=>c});const s=JSON.parse('{"id":"guides/scripting","title":"Lua Scripting","description":"Without Lua scripts, HappyView\'s query endpoints return raw records and procedure endpoints proxy simple creates and updates. Lua scripts let you go much further:","source":"@site/docs/guides/scripting.md","sourceDirName":"guides","slug":"/guides/scripting","permalink":"/guides/scripting","draft":false,"unlisted":false,"tags":[],"version":"current","frontMatter":{},"sidebar":"docs","previous":{"title":"Lexicons","permalink":"/guides/lexicons"},"next":{"title":"Backfill","permalink":"/guides/backfill"}}');var l=n(4848),d=n(8453);const i={},a="Lua Scripting",t={},c=[{value:"Script structure",id:"script-structure",level:2},{value:"Sandbox",id:"sandbox",level:2},{value:"Context globals",id:"context-globals",level:2},{value:"Procedure globals",id:"procedure-globals",level:3},{value:"Query globals",id:"query-globals",level:3},{value:"Utility globals",id:"utility-globals",level:2},{value:"toarray",id:"toarray",level:3},{value:"Record API",id:"record-api",level:2},{value:"Constructor",id:"constructor",level:3},{value:"Static methods",id:"static-methods",level:3},{value:"Instance methods",id:"instance-methods",level:3},{value:"Instance fields",id:"instance-fields",level:3},{value:"Schema validation",id:"schema-validation",level:3},{value:"Save behavior",id:"save-behavior",level:3},{value:"Database API",id:"database-api",level:2},{value:"db.query",id:"dbquery",level:3},{value:"db.get",id:"dbget",level:3},{value:"db.search",id:"dbsearch",level:3},{value:"db.backlinks",id:"dbbacklinks",level:3},{value:"db.count",id:"dbcount",level:3},{value:"db.raw",id:"dbraw",level:3},{value:"Standard libraries",id:"standard-libraries",level:2},{value:"Debugging",id:"debugging",level:2},{value:"Logging",id:"logging",level:3},{value:"Error messages",id:"error-messages",level:3},{value:"Common mistakes",id:"common-mistakes",level:3},{value:"Example scripts",id:"example-scripts",level:2},{value:"Next steps",id:"next-steps",level:2}];function h(e){const r={a:"a",code:"code",h1:"h1",h2:"h2",h3:"h3",header:"header",li:"li",p:"p",pre:"pre",strong:"strong",table:"table",tbody:"tbody",td:"td",th:"th",thead:"thead",tr:"tr",ul:"ul",...(0,d.R)(),...e.components},{Details:n}=r;return n||function(e,r){throw new Error("Expected "+(r?"component":"object")+" `"+e+"` to be defined: you likely forgot to import, pass, or provide it.")}("Details",!0),(0,l.jsxs)(l.Fragment,{children:[(0,l.jsx)(r.header,{children:(0,l.jsx)(r.h1,{id:"lua-scripting",children:"Lua Scripting"})}),"\n",(0,l.jsx)(r.p,{children:"Without Lua scripts, HappyView's query endpoints return raw records and procedure endpoints proxy simple creates and updates. Lua scripts let you go much further:"}),"\n",(0,l.jsxs)(r.ul,{children:["\n",(0,l.jsx)(r.li,{children:"Add filtering logic"}),"\n",(0,l.jsx)(r.li,{children:"Transform responses"}),"\n",(0,l.jsx)(r.li,{children:"Validate input"}),"\n",(0,l.jsx)(r.li,{children:"Compose multi-record operations"}),"\n",(0,l.jsx)(r.li,{children:"Build entirely custom behavior"}),"\n"]}),"\n",(0,l.jsxs)(r.p,{children:["Scripts are attached to query and procedure lexicons and run in a sandboxed Lua VM with access to the ",(0,l.jsx)(r.a,{href:"#record-api",children:"Record API"}),", a ",(0,l.jsx)(r.a,{href:"#database-api",children:"read-only database API"}),", and a set of ",(0,l.jsx)(r.a,{href:"#context-globals",children:"context globals"}),"."]}),"\n",(0,l.jsx)(r.h2,{id:"script-structure",children:"Script structure"}),"\n",(0,l.jsxs)(r.p,{children:["Every script must define a ",(0,l.jsx)(r.code,{children:"handle()"})," function. HappyView calls it when the XRPC endpoint is hit and returns its result as JSON to the client."]}),"\n",(0,l.jsx)(r.pre,{children:(0,l.jsx)(r.code,{className:"language-lua",children:'function handle()\n -- your logic here\n return { key = "value" }\nend\n'})}),"\n",(0,l.jsxs)(r.p,{children:["You can define helper functions and variables outside ",(0,l.jsx)(r.code,{children:"handle()"}),". They're evaluated once when the script loads, then ",(0,l.jsx)(r.code,{children:"handle()"})," is called per request."]}),"\n",(0,l.jsx)(r.h2,{id:"sandbox",children:"Sandbox"}),"\n",(0,l.jsxs)(r.p,{children:["Scripts run in a restricted environment. The following standard Lua modules are ",(0,l.jsx)(r.strong,{children:"removed"})," and unavailable:"]}),"\n",(0,l.jsxs)(r.p,{children:[(0,l.jsx)(r.code,{children:"os"}),", ",(0,l.jsx)(r.code,{children:"io"}),", ",(0,l.jsx)(r.code,{children:"debug"}),", ",(0,l.jsx)(r.code,{children:"package"}),", ",(0,l.jsx)(r.code,{children:"require"}),", ",(0,l.jsx)(r.code,{children:"dofile"}),", ",(0,l.jsx)(r.code,{children:"loadfile"}),", ",(0,l.jsx)(r.code,{children:"load"}),", ",(0,l.jsx)(r.code,{children:"collectgarbage"})]}),"\n",(0,l.jsx)(r.p,{children:"An instruction limit of 1,000,000 prevents infinite loops. Exceeding it terminates the script with an error."}),"\n",(0,l.jsx)(r.h2,{id:"context-globals",children:"Context globals"}),"\n",(0,l.jsxs)(r.p,{children:["These globals are set automatically before ",(0,l.jsx)(r.code,{children:"handle()"})," is called."]}),"\n",(0,l.jsx)(r.h3,{id:"procedure-globals",children:"Procedure globals"}),"\n",(0,l.jsxs)(r.table,{children:[(0,l.jsx)(r.thead,{children:(0,l.jsxs)(r.tr,{children:[(0,l.jsx)(r.th,{children:"Global"}),(0,l.jsx)(r.th,{children:"Type"}),(0,l.jsx)(r.th,{children:"Description"})]})}),(0,l.jsxs)(r.tbody,{children:[(0,l.jsxs)(r.tr,{children:[(0,l.jsx)(r.td,{children:(0,l.jsx)(r.code,{children:"method"})}),(0,l.jsx)(r.td,{children:"string"}),(0,l.jsxs)(r.td,{children:["The XRPC method name (e.g. ",(0,l.jsx)(r.code,{children:"xyz.statusphere.setStatus"}),")"]})]}),(0,l.jsxs)(r.tr,{children:[(0,l.jsx)(r.td,{children:(0,l.jsx)(r.code,{children:"input"})}),(0,l.jsx)(r.td,{children:"table"}),(0,l.jsx)(r.td,{children:"Parsed JSON request body"})]}),(0,l.jsxs)(r.tr,{children:[(0,l.jsx)(r.td,{children:(0,l.jsx)(r.code,{children:"caller_did"})}),(0,l.jsx)(r.td,{children:"string"}),(0,l.jsx)(r.td,{children:"DID of the authenticated user"})]}),(0,l.jsxs)(r.tr,{children:[(0,l.jsx)(r.td,{children:(0,l.jsx)(r.code,{children:"collection"})}),(0,l.jsx)(r.td,{children:"string"}),(0,l.jsx)(r.td,{children:"Target collection NSID"})]})]})]}),"\n",(0,l.jsx)(r.h3,{id:"query-globals",children:"Query globals"}),"\n",(0,l.jsxs)(r.table,{children:[(0,l.jsx)(r.thead,{children:(0,l.jsxs)(r.tr,{children:[(0,l.jsx)(r.th,{children:"Global"}),(0,l.jsx)(r.th,{children:"Type"}),(0,l.jsx)(r.th,{children:"Description"})]})}),(0,l.jsxs)(r.tbody,{children:[(0,l.jsxs)(r.tr,{children:[(0,l.jsx)(r.td,{children:(0,l.jsx)(r.code,{children:"method"})}),(0,l.jsx)(r.td,{children:"string"}),(0,l.jsx)(r.td,{children:"The XRPC method name"})]}),(0,l.jsxs)(r.tr,{children:[(0,l.jsx)(r.td,{children:(0,l.jsx)(r.code,{children:"params"})}),(0,l.jsx)(r.td,{children:"table"}),(0,l.jsx)(r.td,{children:"Query string parameters (all values are strings)"})]}),(0,l.jsxs)(r.tr,{children:[(0,l.jsx)(r.td,{children:(0,l.jsx)(r.code,{children:"collection"})}),(0,l.jsx)(r.td,{children:"string"}),(0,l.jsx)(r.td,{children:"Target collection NSID"})]})]})]}),"\n",(0,l.jsxs)(r.p,{children:["Queries are unauthenticated: there is no ",(0,l.jsx)(r.code,{children:"caller_did"})," or ",(0,l.jsx)(r.code,{children:"input"}),"."]}),"\n",(0,l.jsx)(r.h2,{id:"utility-globals",children:"Utility globals"}),"\n",(0,l.jsx)(r.p,{children:"Available in both queries and procedures:"}),"\n",(0,l.jsxs)(r.table,{children:[(0,l.jsx)(r.thead,{children:(0,l.jsxs)(r.tr,{children:[(0,l.jsx)(r.th,{children:"Function"}),(0,l.jsx)(r.th,{children:"Returns"}),(0,l.jsx)(r.th,{children:"Description"})]})}),(0,l.jsxs)(r.tbody,{children:[(0,l.jsxs)(r.tr,{children:[(0,l.jsx)(r.td,{children:(0,l.jsx)(r.code,{children:"now()"})}),(0,l.jsx)(r.td,{children:"string"}),(0,l.jsx)(r.td,{children:"Current UTC timestamp in ISO 8601 format"})]}),(0,l.jsxs)(r.tr,{children:[(0,l.jsx)(r.td,{children:(0,l.jsx)(r.code,{children:"log(message)"})}),(0,l.jsx)(r.td,{children:"\u2014"}),(0,l.jsx)(r.td,{children:"Log a message (appears in server logs at debug level)"})]}),(0,l.jsxs)(r.tr,{children:[(0,l.jsx)(r.td,{children:(0,l.jsx)(r.code,{children:"TID()"})}),(0,l.jsx)(r.td,{children:"string"}),(0,l.jsx)(r.td,{children:"Generate a fresh AT Protocol TID (13-character sortable identifier)"})]}),(0,l.jsxs)(r.tr,{children:[(0,l.jsx)(r.td,{children:(0,l.jsx)(r.code,{children:"toarray(table)"})}),(0,l.jsx)(r.td,{children:"table"}),(0,l.jsxs)(r.td,{children:["Mark a table as a JSON array for serialization (see ",(0,l.jsx)(r.a,{href:"#toarray",children:"below"}),")"]})]})]})]}),"\n",(0,l.jsx)(r.h3,{id:"toarray",children:"toarray"}),"\n",(0,l.jsxs)(r.p,{children:["Lua tables don't distinguish between arrays and objects. When a table is serialized to JSON, an empty table ",(0,l.jsx)(r.code,{children:"{}"})," becomes a JSON object ",(0,l.jsx)(r.code,{children:"{}"})," instead of an array ",(0,l.jsx)(r.code,{children:"[]"}),". The ",(0,l.jsx)(r.code,{children:"toarray()"})," function marks a table so it always serializes as a JSON array \u2014 even when empty."]}),"\n",(0,l.jsx)(r.pre,{children:(0,l.jsx)(r.code,{className:"language-lua",children:'return { items = toarray(results) }\n-- With results: [{"name": "a"}, {"name": "b"}]\n-- Without results: {"items": []} (not {"items": {}})\n'})}),"\n",(0,l.jsxs)(r.p,{children:["You don't need ",(0,l.jsx)(r.code,{children:"toarray()"})," on results from ",(0,l.jsx)(r.code,{children:"db.query"}),", ",(0,l.jsx)(r.code,{children:"db.search"}),", ",(0,l.jsx)(r.code,{children:"db.backlinks"}),", or ",(0,l.jsx)(r.code,{children:"db.raw"})," \u2014 those already return properly marked arrays. Use it when you build a table yourself with ",(0,l.jsx)(r.code,{children:"table.insert()"}),"."]}),"\n",(0,l.jsx)(r.h2,{id:"record-api",children:"Record API"}),"\n",(0,l.jsxs)(r.p,{children:["The ",(0,l.jsx)(r.code,{children:"Record"})," API is only available in ",(0,l.jsx)(r.strong,{children:"procedure"})," scripts. It handles creating, updating, loading, and deleting AT Protocol records. Writes are proxied to the caller's PDS and indexed locally."]}),"\n",(0,l.jsx)(r.h3,{id:"constructor",children:"Constructor"}),"\n",(0,l.jsx)(r.pre,{children:(0,l.jsx)(r.code,{className:"language-lua",children:'local r = Record("xyz.statusphere.status", { status = "\\ud83d\\ude0a", createdAt = now() })\n'})}),"\n",(0,l.jsxs)(r.p,{children:["Creates a new record instance for the given collection. The optional second argument sets initial field values. The record's ",(0,l.jsx)(r.code,{children:"_key_type"})," is automatically set from the lexicon's ",(0,l.jsx)(r.code,{children:"key"})," definition. Default values from the schema are populated for any missing fields."]}),"\n",(0,l.jsx)(r.h3,{id:"static-methods",children:"Static methods"}),"\n",(0,l.jsx)(r.pre,{children:(0,l.jsx)(r.code,{className:"language-lua",children:'-- Save multiple records in parallel\nRecord.save_all({ record1, record2, record3 })\n\n-- Load a record from the local database by AT URI\nlocal r = Record.load("at://did:plc:abc/xyz.statusphere.status/abc123")\n-- Returns nil if not found\n\n-- Load multiple records in parallel\nlocal records = Record.load_all({ uri1, uri2 })\n-- Returns nil entries for URIs not found\n'})}),"\n",(0,l.jsx)(r.h3,{id:"instance-methods",children:"Instance methods"}),"\n",(0,l.jsx)(r.pre,{children:(0,l.jsx)(r.code,{className:"language-lua",children:'-- Save (creates or updates depending on whether _uri is set)\nr:save()\n\n-- Delete from PDS and local database\nr:delete()\n\n-- Set the record key type (tid, any, nsid, or literal:*)\nr:set_key_type("tid")\n\n-- Set a specific record key\nr:set_rkey("my-key")\n\n-- Auto-generate a record key based on _key_type\nlocal key = r:generate_rkey()\n'})}),"\n",(0,l.jsx)(r.p,{children:(0,l.jsxs)(r.strong,{children:["Key type behavior for ",(0,l.jsx)(r.code,{children:"generate_rkey()"}),":"]})}),"\n",(0,l.jsxs)(r.table,{children:[(0,l.jsx)(r.thead,{children:(0,l.jsxs)(r.tr,{children:[(0,l.jsx)(r.th,{children:"Key type"}),(0,l.jsx)(r.th,{children:"Generated rkey"})]})}),(0,l.jsxs)(r.tbody,{children:[(0,l.jsxs)(r.tr,{children:[(0,l.jsx)(r.td,{children:(0,l.jsx)(r.code,{children:"tid"})}),(0,l.jsx)(r.td,{children:"Sortable timestamp-based ID"})]}),(0,l.jsxs)(r.tr,{children:[(0,l.jsx)(r.td,{children:(0,l.jsx)(r.code,{children:"any"})}),(0,l.jsxs)(r.td,{children:["Same as ",(0,l.jsx)(r.code,{children:"tid"})]})]}),(0,l.jsxs)(r.tr,{children:[(0,l.jsx)(r.td,{children:(0,l.jsx)(r.code,{children:"literal:value"})}),(0,l.jsx)(r.td,{children:"The literal value after the colon"})]}),(0,l.jsxs)(r.tr,{children:[(0,l.jsx)(r.td,{children:(0,l.jsx)(r.code,{children:"nsid"})}),(0,l.jsxs)(r.td,{children:["Error \u2014 use ",(0,l.jsx)(r.code,{children:"set_rkey()"})," instead"]})]})]})]}),"\n",(0,l.jsx)(r.h3,{id:"instance-fields",children:"Instance fields"}),"\n",(0,l.jsx)(r.p,{children:"These fields are set automatically and are read-only (writes raise an error):"}),"\n",(0,l.jsxs)(r.table,{children:[(0,l.jsx)(r.thead,{children:(0,l.jsxs)(r.tr,{children:[(0,l.jsx)(r.th,{children:"Field"}),(0,l.jsx)(r.th,{children:"Type"}),(0,l.jsx)(r.th,{children:"Description"})]})}),(0,l.jsxs)(r.tbody,{children:[(0,l.jsxs)(r.tr,{children:[(0,l.jsx)(r.td,{children:(0,l.jsx)(r.code,{children:"_uri"})}),(0,l.jsx)(r.td,{children:"string?"}),(0,l.jsxs)(r.td,{children:["AT URI \u2014 set after ",(0,l.jsx)(r.code,{children:"save()"}),", cleared after ",(0,l.jsx)(r.code,{children:"delete()"})]})]}),(0,l.jsxs)(r.tr,{children:[(0,l.jsx)(r.td,{children:(0,l.jsx)(r.code,{children:"_cid"})}),(0,l.jsx)(r.td,{children:"string?"}),(0,l.jsxs)(r.td,{children:["Content hash \u2014 set after ",(0,l.jsx)(r.code,{children:"save()"}),", cleared after ",(0,l.jsx)(r.code,{children:"delete()"})]})]}),(0,l.jsxs)(r.tr,{children:[(0,l.jsx)(r.td,{children:(0,l.jsx)(r.code,{children:"_key_type"})}),(0,l.jsx)(r.td,{children:"string?"}),(0,l.jsx)(r.td,{children:"Record key type from the lexicon definition"})]}),(0,l.jsxs)(r.tr,{children:[(0,l.jsx)(r.td,{children:(0,l.jsx)(r.code,{children:"_rkey"})}),(0,l.jsx)(r.td,{children:"string?"}),(0,l.jsxs)(r.td,{children:["Record key \u2014 set via ",(0,l.jsx)(r.code,{children:"set_rkey()"})," or ",(0,l.jsx)(r.code,{children:"generate_rkey()"})]})]}),(0,l.jsxs)(r.tr,{children:[(0,l.jsx)(r.td,{children:(0,l.jsx)(r.code,{children:"_collection"})}),(0,l.jsx)(r.td,{children:"string"}),(0,l.jsx)(r.td,{children:"Collection NSID (always set)"})]}),(0,l.jsxs)(r.tr,{children:[(0,l.jsx)(r.td,{children:(0,l.jsx)(r.code,{children:"_schema"})}),(0,l.jsx)(r.td,{children:"table?"}),(0,l.jsx)(r.td,{children:"Schema definition from the lexicon (used for validation)"})]})]})]}),"\n",(0,l.jsx)(r.h3,{id:"schema-validation",children:"Schema validation"}),"\n",(0,l.jsx)(r.p,{children:"When a record has a schema (loaded from the lexicon):"}),"\n",(0,l.jsxs)(r.ul,{children:["\n",(0,l.jsxs)(r.li,{children:[(0,l.jsx)(r.strong,{children:"On save:"})," required fields are checked, and missing required fields raise an error"]}),"\n",(0,l.jsxs)(r.li,{children:[(0,l.jsx)(r.strong,{children:"On construction:"})," default values from schema properties are auto-populated"]}),"\n",(0,l.jsxs)(r.li,{children:[(0,l.jsx)(r.strong,{children:"On save:"})," only fields defined in the schema's ",(0,l.jsx)(r.code,{children:"properties"})," are sent to the PDS"]}),"\n"]}),"\n",(0,l.jsx)(r.h3,{id:"save-behavior",children:"Save behavior"}),"\n",(0,l.jsxs)(r.p,{children:[(0,l.jsx)(r.code,{children:"r:save()"})," auto-detects create vs update:"]}),"\n",(0,l.jsxs)(r.ul,{children:["\n",(0,l.jsxs)(r.li,{children:["If ",(0,l.jsx)(r.code,{children:"_uri"})," is nil \u2192 calls ",(0,l.jsx)(r.code,{children:"createRecord"})," on the PDS"]}),"\n",(0,l.jsxs)(r.li,{children:["If ",(0,l.jsx)(r.code,{children:"_uri"})," is set \u2192 calls ",(0,l.jsx)(r.code,{children:"putRecord"})," on the PDS"]}),"\n"]}),"\n",(0,l.jsxs)(r.p,{children:["After a successful save, ",(0,l.jsx)(r.code,{children:"_uri"})," and ",(0,l.jsx)(r.code,{children:"_cid"})," are updated on the record instance."]}),"\n",(0,l.jsx)(r.h2,{id:"database-api",children:"Database API"}),"\n",(0,l.jsxs)(r.p,{children:["The ",(0,l.jsx)(r.code,{children:"db"})," table provides read-only access to indexed records. Available in both queries and procedures."]}),"\n",(0,l.jsx)(r.h3,{id:"dbquery",children:"db.query"}),"\n",(0,l.jsx)(r.pre,{children:(0,l.jsx)(r.code,{className:"language-lua",children:'local result = db.query({\n collection = "xyz.statusphere.status", -- required\n did = "did:plc:abc", -- optional: filter by DID\n limit = 20, -- optional: max 100, default 20\n offset = 0, -- optional: for pagination\n})\n\n-- result.records \u2014 array of record tables (each includes a "uri" field)\n-- result.cursor \u2014 present when more records exist\n'})}),"\n",(0,l.jsx)(r.h3,{id:"dbget",children:"db.get"}),"\n",(0,l.jsx)(r.pre,{children:(0,l.jsx)(r.code,{className:"language-lua",children:'local record = db.get("at://did:plc:abc/xyz.statusphere.status/abc123")\n-- Returns the record table or nil\n-- The returned table includes a "uri" field\n'})}),"\n",(0,l.jsx)(r.h3,{id:"dbsearch",children:"db.search"}),"\n",(0,l.jsx)(r.pre,{children:(0,l.jsx)(r.code,{className:"language-lua",children:'local result = db.search({\n collection = "xyz.statusphere.status", -- required\n field = "displayName", -- required: record field to search\n query = "alice", -- required: search term\n limit = 10, -- optional: max 100, default 10\n})\n\n-- result.records \u2014 array of matching records, ranked by relevance:\n-- exact match > prefix match > contains match, then alphabetical\n'})}),"\n",(0,l.jsx)(r.h3,{id:"dbbacklinks",children:"db.backlinks"}),"\n",(0,l.jsx)(r.p,{children:"Find records that reference a given AT URI anywhere in their data. Useful for finding likes on a post, replies to a thread, or any record that links to another."}),"\n",(0,l.jsx)(r.pre,{children:(0,l.jsx)(r.code,{className:"language-lua",children:'local result = db.backlinks({\n collection = "xyz.statusphere.status", -- required\n uri = "at://did:plc:abc/xyz.statusphere.status/foo", -- required: the URI to find references to\n did = "did:plc:abc", -- optional: filter by DID\n limit = 20, -- optional: max 100, default 20\n offset = 0, -- optional: for pagination\n})\n\n-- result.records \u2014 array of records whose data contains the given URI\n-- result.cursor \u2014 present when more records exist\n'})}),"\n",(0,l.jsxs)(r.p,{children:["The search checks the full record data, so it works regardless of which field holds the reference (",(0,l.jsx)(r.code,{children:"subject"}),", ",(0,l.jsx)(r.code,{children:"parent"}),", ",(0,l.jsx)(r.code,{children:"reply.root"}),", etc.)."]}),"\n",(0,l.jsx)(r.h3,{id:"dbcount",children:"db.count"}),"\n",(0,l.jsx)(r.pre,{children:(0,l.jsx)(r.code,{className:"language-lua",children:'local n = db.count("xyz.statusphere.status")\nlocal n = db.count("xyz.statusphere.status", "did:plc:abc") -- filter by DID\n'})}),"\n",(0,l.jsx)(r.h3,{id:"dbraw",children:"db.raw"}),"\n",(0,l.jsxs)(r.p,{children:["Run a raw SQL query against the database. Only ",(0,l.jsx)(r.code,{children:"SELECT"})," statements are allowed."]}),"\n",(0,l.jsx)(r.pre,{children:(0,l.jsx)(r.code,{className:"language-lua",children:'local rows = db.raw(\n "SELECT uri, did, record FROM records WHERE collection = $1 AND did = $2 LIMIT $3",\n { "xyz.statusphere.status", "did:plc:abc", 10 }\n)\n\nfor _, row in ipairs(rows) do\n -- row.uri, row.did, row.record (JSONB is returned as a Lua table)\nend\n'})}),"\n",(0,l.jsxs)(r.p,{children:["Parameters are passed as an array and bound to ",(0,l.jsx)(r.code,{children:"$1"}),", ",(0,l.jsx)(r.code,{children:"$2"}),", etc. Supported parameter types: strings, integers, numbers, booleans, and nil."]}),"\n",(0,l.jsx)(r.p,{children:"Column types are mapped automatically:"}),"\n",(0,l.jsxs)(r.table,{children:[(0,l.jsx)(r.thead,{children:(0,l.jsxs)(r.tr,{children:[(0,l.jsx)(r.th,{children:"Postgres type"}),(0,l.jsx)(r.th,{children:"Lua type"})]})}),(0,l.jsxs)(r.tbody,{children:[(0,l.jsxs)(r.tr,{children:[(0,l.jsxs)(r.td,{children:[(0,l.jsx)(r.code,{children:"TEXT"}),", ",(0,l.jsx)(r.code,{children:"VARCHAR"})]}),(0,l.jsx)(r.td,{children:"string"})]}),(0,l.jsxs)(r.tr,{children:[(0,l.jsxs)(r.td,{children:[(0,l.jsx)(r.code,{children:"INT4"}),", ",(0,l.jsx)(r.code,{children:"INT8"})]}),(0,l.jsx)(r.td,{children:"integer"})]}),(0,l.jsxs)(r.tr,{children:[(0,l.jsxs)(r.td,{children:[(0,l.jsx)(r.code,{children:"FLOAT4"}),", ",(0,l.jsx)(r.code,{children:"FLOAT8"})]}),(0,l.jsx)(r.td,{children:"number"})]}),(0,l.jsxs)(r.tr,{children:[(0,l.jsx)(r.td,{children:(0,l.jsx)(r.code,{children:"BOOL"})}),(0,l.jsx)(r.td,{children:"boolean"})]}),(0,l.jsxs)(r.tr,{children:[(0,l.jsxs)(r.td,{children:[(0,l.jsx)(r.code,{children:"JSON"}),", ",(0,l.jsx)(r.code,{children:"JSONB"})]}),(0,l.jsx)(r.td,{children:"table"})]}),(0,l.jsxs)(r.tr,{children:[(0,l.jsx)(r.td,{children:(0,l.jsx)(r.code,{children:"TIMESTAMPTZ"})}),(0,l.jsx)(r.td,{children:"string (ISO 8601)"})]}),(0,l.jsxs)(r.tr,{children:[(0,l.jsx)(r.td,{children:"Other"}),(0,l.jsx)(r.td,{children:"string (fallback)"})]})]})]}),"\n",(0,l.jsx)(r.h2,{id:"standard-libraries",children:"Standard libraries"}),"\n",(0,l.jsx)(r.p,{children:"The following Lua 5.4 standard library modules are available:"}),"\n",(0,l.jsxs)(n,{children:[(0,l.jsx)("summary",{children:(0,l.jsx)(r.p,{children:(0,l.jsx)(r.code,{children:"string"})})}),(0,l.jsxs)(r.ul,{children:["\n",(0,l.jsx)(r.li,{children:(0,l.jsx)(r.a,{href:"https://lua.org/manual/5.4/manual.html#pdf-string.byte",children:(0,l.jsx)(r.code,{children:"byte"})})}),"\n",(0,l.jsx)(r.li,{children:(0,l.jsx)(r.a,{href:"https://lua.org/manual/5.4/manual.html#pdf-string.char",children:(0,l.jsx)(r.code,{children:"char"})})}),"\n",(0,l.jsx)(r.li,{children:(0,l.jsx)(r.a,{href:"https://lua.org/manual/5.4/manual.html#pdf-string.find",children:(0,l.jsx)(r.code,{children:"find"})})}),"\n",(0,l.jsx)(r.li,{children:(0,l.jsx)(r.a,{href:"https://lua.org/manual/5.4/manual.html#pdf-string.format",children:(0,l.jsx)(r.code,{children:"format"})})}),"\n",(0,l.jsx)(r.li,{children:(0,l.jsx)(r.a,{href:"https://lua.org/manual/5.4/manual.html#pdf-string.gmatch",children:(0,l.jsx)(r.code,{children:"gmatch"})})}),"\n",(0,l.jsx)(r.li,{children:(0,l.jsx)(r.a,{href:"https://lua.org/manual/5.4/manual.html#pdf-string.gsub",children:(0,l.jsx)(r.code,{children:"gsub"})})}),"\n",(0,l.jsx)(r.li,{children:(0,l.jsx)(r.a,{href:"https://lua.org/manual/5.4/manual.html#pdf-string.len",children:(0,l.jsx)(r.code,{children:"len"})})}),"\n",(0,l.jsx)(r.li,{children:(0,l.jsx)(r.a,{href:"https://lua.org/manual/5.4/manual.html#pdf-string.lower",children:(0,l.jsx)(r.code,{children:"lower"})})}),"\n",(0,l.jsx)(r.li,{children:(0,l.jsx)(r.a,{href:"https://lua.org/manual/5.4/manual.html#pdf-string.match",children:(0,l.jsx)(r.code,{children:"match"})})}),"\n",(0,l.jsx)(r.li,{children:(0,l.jsx)(r.a,{href:"https://lua.org/manual/5.4/manual.html#pdf-string.rep",children:(0,l.jsx)(r.code,{children:"rep"})})}),"\n",(0,l.jsx)(r.li,{children:(0,l.jsx)(r.a,{href:"https://lua.org/manual/5.4/manual.html#pdf-string.reverse",children:(0,l.jsx)(r.code,{children:"reverse"})})}),"\n",(0,l.jsx)(r.li,{children:(0,l.jsx)(r.a,{href:"https://lua.org/manual/5.4/manual.html#pdf-string.sub",children:(0,l.jsx)(r.code,{children:"sub"})})}),"\n",(0,l.jsx)(r.li,{children:(0,l.jsx)(r.a,{href:"https://lua.org/manual/5.4/manual.html#pdf-string.upper",children:(0,l.jsx)(r.code,{children:"upper"})})}),"\n"]})]}),"\n",(0,l.jsxs)(n,{children:[(0,l.jsx)("summary",{children:(0,l.jsx)(r.p,{children:(0,l.jsx)(r.code,{children:"table"})})}),(0,l.jsxs)(r.ul,{children:["\n",(0,l.jsx)(r.li,{children:(0,l.jsx)(r.a,{href:"https://lua.org/manual/5.4/manual.html#pdf-table.concat",children:(0,l.jsx)(r.code,{children:"concat"})})}),"\n",(0,l.jsx)(r.li,{children:(0,l.jsx)(r.a,{href:"https://lua.org/manual/5.4/manual.html#pdf-table.insert",children:(0,l.jsx)(r.code,{children:"insert"})})}),"\n",(0,l.jsx)(r.li,{children:(0,l.jsx)(r.a,{href:"https://lua.org/manual/5.4/manual.html#pdf-table.remove",children:(0,l.jsx)(r.code,{children:"remove"})})}),"\n",(0,l.jsx)(r.li,{children:(0,l.jsx)(r.a,{href:"https://lua.org/manual/5.4/manual.html#pdf-table.sort",children:(0,l.jsx)(r.code,{children:"sort"})})}),"\n",(0,l.jsx)(r.li,{children:(0,l.jsx)(r.a,{href:"https://lua.org/manual/5.4/manual.html#pdf-table.unpack",children:(0,l.jsx)(r.code,{children:"unpack"})})}),"\n"]})]}),"\n",(0,l.jsxs)(n,{children:[(0,l.jsx)("summary",{children:(0,l.jsx)(r.p,{children:(0,l.jsx)(r.code,{children:"math"})})}),(0,l.jsxs)(r.ul,{children:["\n",(0,l.jsx)(r.li,{children:(0,l.jsx)(r.a,{href:"https://lua.org/manual/5.4/manual.html#pdf-math.abs",children:(0,l.jsx)(r.code,{children:"abs"})})}),"\n",(0,l.jsx)(r.li,{children:(0,l.jsx)(r.a,{href:"https://lua.org/manual/5.4/manual.html#pdf-math.ceil",children:(0,l.jsx)(r.code,{children:"ceil"})})}),"\n",(0,l.jsx)(r.li,{children:(0,l.jsx)(r.a,{href:"https://lua.org/manual/5.4/manual.html#pdf-math.floor",children:(0,l.jsx)(r.code,{children:"floor"})})}),"\n",(0,l.jsx)(r.li,{children:(0,l.jsx)(r.a,{href:"https://lua.org/manual/5.4/manual.html#pdf-math.max",children:(0,l.jsx)(r.code,{children:"max"})})}),"\n",(0,l.jsx)(r.li,{children:(0,l.jsx)(r.a,{href:"https://lua.org/manual/5.4/manual.html#pdf-math.min",children:(0,l.jsx)(r.code,{children:"min"})})}),"\n",(0,l.jsx)(r.li,{children:(0,l.jsx)(r.a,{href:"https://lua.org/manual/5.4/manual.html#pdf-math.random",children:(0,l.jsx)(r.code,{children:"random"})})}),"\n",(0,l.jsx)(r.li,{children:(0,l.jsx)(r.a,{href:"https://lua.org/manual/5.4/manual.html#pdf-math.sqrt",children:(0,l.jsx)(r.code,{children:"sqrt"})})}),"\n",(0,l.jsx)(r.li,{children:(0,l.jsx)(r.a,{href:"https://lua.org/manual/5.4/manual.html#pdf-math.huge",children:(0,l.jsx)(r.code,{children:"huge"})})}),"\n",(0,l.jsx)(r.li,{children:(0,l.jsx)(r.a,{href:"https://lua.org/manual/5.4/manual.html#pdf-math.pi",children:(0,l.jsx)(r.code,{children:"pi"})})}),"\n"]})]}),"\n",(0,l.jsxs)(n,{children:[(0,l.jsx)("summary",{children:(0,l.jsx)(r.p,{children:"Standard builtins"})}),(0,l.jsxs)(r.ul,{children:["\n",(0,l.jsx)(r.li,{children:(0,l.jsx)(r.a,{href:"https://lua.org/manual/5.4/manual.html#pdf-print",children:(0,l.jsx)(r.code,{children:"print"})})}),"\n",(0,l.jsx)(r.li,{children:(0,l.jsx)(r.a,{href:"https://lua.org/manual/5.4/manual.html#pdf-tostring",children:(0,l.jsx)(r.code,{children:"tostring"})})}),"\n",(0,l.jsx)(r.li,{children:(0,l.jsx)(r.a,{href:"https://lua.org/manual/5.4/manual.html#pdf-tonumber",children:(0,l.jsx)(r.code,{children:"tonumber"})})}),"\n",(0,l.jsx)(r.li,{children:(0,l.jsx)(r.a,{href:"https://lua.org/manual/5.4/manual.html#pdf-type",children:(0,l.jsx)(r.code,{children:"type"})})}),"\n",(0,l.jsx)(r.li,{children:(0,l.jsx)(r.a,{href:"https://lua.org/manual/5.4/manual.html#pdf-pairs",children:(0,l.jsx)(r.code,{children:"pairs"})})}),"\n",(0,l.jsx)(r.li,{children:(0,l.jsx)(r.a,{href:"https://lua.org/manual/5.4/manual.html#pdf-ipairs",children:(0,l.jsx)(r.code,{children:"ipairs"})})}),"\n",(0,l.jsx)(r.li,{children:(0,l.jsx)(r.a,{href:"https://lua.org/manual/5.4/manual.html#pdf-next",children:(0,l.jsx)(r.code,{children:"next"})})}),"\n",(0,l.jsx)(r.li,{children:(0,l.jsx)(r.a,{href:"https://lua.org/manual/5.4/manual.html#pdf-select",children:(0,l.jsx)(r.code,{children:"select"})})}),"\n",(0,l.jsx)(r.li,{children:(0,l.jsx)(r.a,{href:"https://lua.org/manual/5.4/manual.html#pdf-table.unpack",children:(0,l.jsx)(r.code,{children:"unpack"})})}),"\n",(0,l.jsx)(r.li,{children:(0,l.jsx)(r.a,{href:"https://lua.org/manual/5.4/manual.html#pdf-error",children:(0,l.jsx)(r.code,{children:"error"})})}),"\n",(0,l.jsx)(r.li,{children:(0,l.jsx)(r.a,{href:"https://lua.org/manual/5.4/manual.html#pdf-pcall",children:(0,l.jsx)(r.code,{children:"pcall"})})}),"\n",(0,l.jsx)(r.li,{children:(0,l.jsx)(r.a,{href:"https://lua.org/manual/5.4/manual.html#pdf-xpcall",children:(0,l.jsx)(r.code,{children:"xpcall"})})}),"\n",(0,l.jsx)(r.li,{children:(0,l.jsx)(r.a,{href:"https://lua.org/manual/5.4/manual.html#pdf-assert",children:(0,l.jsx)(r.code,{children:"assert"})})}),"\n",(0,l.jsx)(r.li,{children:(0,l.jsx)(r.a,{href:"https://lua.org/manual/5.4/manual.html#pdf-setmetatable",children:(0,l.jsx)(r.code,{children:"setmetatable"})})}),"\n",(0,l.jsx)(r.li,{children:(0,l.jsx)(r.a,{href:"https://lua.org/manual/5.4/manual.html#pdf-getmetatable",children:(0,l.jsx)(r.code,{children:"getmetatable"})})}),"\n",(0,l.jsx)(r.li,{children:(0,l.jsx)(r.a,{href:"https://lua.org/manual/5.4/manual.html#pdf-rawget",children:(0,l.jsx)(r.code,{children:"rawget"})})}),"\n",(0,l.jsx)(r.li,{children:(0,l.jsx)(r.a,{href:"https://lua.org/manual/5.4/manual.html#pdf-rawset",children:(0,l.jsx)(r.code,{children:"rawset"})})}),"\n",(0,l.jsx)(r.li,{children:(0,l.jsx)(r.a,{href:"https://lua.org/manual/5.4/manual.html#pdf-rawequal",children:(0,l.jsx)(r.code,{children:"rawequal"})})}),"\n"]})]}),"\n",(0,l.jsx)(r.h2,{id:"debugging",children:"Debugging"}),"\n",(0,l.jsx)(r.h3,{id:"logging",children:"Logging"}),"\n",(0,l.jsxs)(r.p,{children:["Use ",(0,l.jsx)(r.code,{children:"log()"})," to trace script execution. Output appears in the server logs at ",(0,l.jsx)(r.strong,{children:"debug"})," level with the field ",(0,l.jsx)(r.code,{children:"lua_log"}),":"]}),"\n",(0,l.jsx)(r.pre,{children:(0,l.jsx)(r.code,{className:"language-lua",children:'function handle()\n log("handle called with params: " .. tostring(params.limit))\n local result = db.query({ collection = collection, limit = params.limit })\n log("query returned " .. #result.records .. " records")\n return result\nend\n'})}),"\n",(0,l.jsxs)(r.p,{children:["To see log output, make sure your ",(0,l.jsx)(r.code,{children:"RUST_LOG"})," environment variable includes debug level for HappyView (the default ",(0,l.jsx)(r.code,{children:"happyview=debug"})," works). See ",(0,l.jsx)(r.a,{href:"/getting-started/configuration",children:"Configuration"}),"."]}),"\n",(0,l.jsx)(r.h3,{id:"error-messages",children:"Error messages"}),"\n",(0,l.jsxs)(r.p,{children:["When a script fails, the client receives a generic ",(0,l.jsx)(r.code,{children:"500"})," response:"]}),"\n",(0,l.jsxs)(r.ul,{children:["\n",(0,l.jsxs)(r.li,{children:[(0,l.jsx)(r.code,{children:'{"error": "script execution failed"}'}),": covers syntax errors, runtime errors, missing ",(0,l.jsx)(r.code,{children:"handle()"})," function, and errors raised with ",(0,l.jsx)(r.code,{children:"error()"})]}),"\n",(0,l.jsxs)(r.li,{children:[(0,l.jsx)(r.code,{children:'{"error": "script exceeded execution time limit"}'}),": the script hit the 1,000,000 instruction limit"]}),"\n"]}),"\n",(0,l.jsxs)(r.p,{children:["The ",(0,l.jsx)(r.strong,{children:"full error message"})," is logged server-side at error level. Check the server logs to see the actual Lua error, including line numbers and stack traces."]}),"\n",(0,l.jsx)(r.h3,{id:"common-mistakes",children:"Common mistakes"}),"\n",(0,l.jsxs)(r.ul,{children:["\n",(0,l.jsxs)(r.li,{children:[(0,l.jsxs)(r.strong,{children:["Missing ",(0,l.jsx)(r.code,{children:"handle()"})," function"]}),": Every script must define a global ",(0,l.jsx)(r.code,{children:"handle()"}),' function. If it\'s missing or misspelled, the script fails silently with "script execution failed".']}),"\n",(0,l.jsxs)(r.li,{children:[(0,l.jsxs)(r.strong,{children:["Calling ",(0,l.jsx)(r.code,{children:"error()"})," for expected conditions"]}),": Lua's ",(0,l.jsx)(r.code,{children:"error()"}),' triggers a 500 response. For expected conditions like "record not found", return a structured error response instead: ',(0,l.jsx)(r.code,{children:'return { error = "not found" }'}),"."]}),"\n",(0,l.jsxs)(r.li,{children:[(0,l.jsx)(r.strong,{children:"Infinite loops"}),": The sandbox enforces a 1,000,000 instruction limit. If your script processes large data sets, paginate with ",(0,l.jsx)(r.code,{children:"db.query()"})," limits instead of loading everything at once."]}),"\n",(0,l.jsxs)(r.li,{children:[(0,l.jsxs)(r.strong,{children:["Forgetting ",(0,l.jsx)(r.code,{children:"params"})," values are strings"]}),": All query string parameters arrive as strings. Use ",(0,l.jsx)(r.code,{children:"tonumber(params.limit)"})," if you need a number."]}),"\n"]}),"\n",(0,l.jsx)(r.h2,{id:"example-scripts",children:"Example scripts"}),"\n",(0,l.jsx)(r.p,{children:"See the example script references for complete, ready-to-use scripts:"}),"\n",(0,l.jsx)(r.p,{children:(0,l.jsx)(r.strong,{children:"Queries:"})}),"\n",(0,l.jsxs)(r.ul,{children:["\n",(0,l.jsxs)(r.li,{children:[(0,l.jsx)(r.a,{href:"/reference/scripts/get-record",children:"Get a record"})," \u2014 fetch a single record by AT URI"]}),"\n",(0,l.jsxs)(r.li,{children:[(0,l.jsx)(r.a,{href:"/reference/scripts/paginated-list",children:"Paginated list"})," \u2014 list records with cursor-based pagination and DID filtering"]}),"\n",(0,l.jsxs)(r.li,{children:[(0,l.jsx)(r.a,{href:"/reference/scripts/list-or-fetch",children:"List or fetch"})," \u2014 combined single-record lookup and paginated listing"]}),"\n",(0,l.jsxs)(r.li,{children:[(0,l.jsx)(r.a,{href:"/reference/scripts/expanded-query",children:"Expanded query"})," \u2014 list statuses with user profiles in a single response"]}),"\n"]}),"\n",(0,l.jsx)(r.p,{children:(0,l.jsx)(r.strong,{children:"Procedures:"})}),"\n",(0,l.jsxs)(r.ul,{children:["\n",(0,l.jsxs)(r.li,{children:[(0,l.jsx)(r.a,{href:"/reference/scripts/create-record",children:"Create a record"})," \u2014 simple write that saves input as a record"]}),"\n",(0,l.jsxs)(r.li,{children:[(0,l.jsx)(r.a,{href:"/reference/scripts/upsert-record",children:"Upsert a record"})," \u2014 create or update using a deterministic rkey"]}),"\n",(0,l.jsxs)(r.li,{children:[(0,l.jsx)(r.a,{href:"/reference/scripts/update-or-delete",children:"Update or delete"})," \u2014 single endpoint handling create, update, and delete"]}),"\n",(0,l.jsxs)(r.li,{children:[(0,l.jsx)(r.a,{href:"/reference/scripts/batch-save",children:"Batch save"})," \u2014 create multiple records in parallel with ",(0,l.jsx)(r.code,{children:"Record.save_all()"})]}),"\n",(0,l.jsxs)(r.li,{children:[(0,l.jsx)(r.a,{href:"/reference/scripts/sidecar-records",children:"Sidecar records"})," \u2014 create linked records across collections with a shared rkey"]}),"\n",(0,l.jsxs)(r.li,{children:[(0,l.jsx)(r.a,{href:"/reference/scripts/cascading-delete",children:"Cascading delete"})," \u2014 delete a record and all related records"]}),"\n",(0,l.jsxs)(r.li,{children:[(0,l.jsx)(r.a,{href:"/reference/scripts/complex-mutations",children:"Complex mutations"})," \u2014 load, transform, and save a record with multiple field changes"]}),"\n"]}),"\n",(0,l.jsx)(r.h2,{id:"next-steps",children:"Next steps"}),"\n",(0,l.jsxs)(r.ul,{children:["\n",(0,l.jsxs)(r.li,{children:[(0,l.jsx)(r.a,{href:"/guides/lexicons",children:"Lexicons"}),": Understand how record, query, and procedure lexicons work together"]}),"\n",(0,l.jsxs)(r.li,{children:[(0,l.jsx)(r.a,{href:"/reference/xrpc-api",children:"XRPC API"}),": See how endpoints behave with and without Lua scripts"]}),"\n",(0,l.jsxs)(r.li,{children:[(0,l.jsx)(r.a,{href:"/getting-started/dashboard#lua-editor",children:"Dashboard"}),": Use the web editor with context-aware completions"]}),"\n"]})]})}function o(e={}){const{wrapper:r}={...(0,d.R)(),...e.components};return r?(0,l.jsx)(r,{...e,children:(0,l.jsx)(h,{...e})}):h(e)}},8453(e,r,n){n.d(r,{R:()=>i,x:()=>a});var s=n(6540);const l={},d=s.createContext(l);function i(e){const r=s.useContext(d);return s.useMemo(function(){return"function"==typeof e?e(r):{...r,...e}},[r,e])}function a(e){let r;return r=e.disableParentContext?"function"==typeof e.components?e.components(l):e.components||l:i(e.components),s.createElement(d.Provider,{value:r},e.children)}}}]); \ No newline at end of file diff --git a/assets/js/920ba7ef.b825c418.js b/assets/js/920ba7ef.b825c418.js deleted file mode 100644 --- a/assets/js/920ba7ef.b825c418.js +++ /dev/null @@ -1,1 +0,0 @@ -"use strict";(self.webpackChunkhappyview_docs=self.webpackChunkhappyview_docs||[]).push([[5953],{1189(e,r,n){n.r(r),n.d(r,{assets:()=>a,contentTitle:()=>t,default:()=>o,frontMatter:()=>d,metadata:()=>s,toc:()=>c});const s=JSON.parse('{"id":"guides/scripting","title":"Lua Scripting","description":"Without Lua scripts, HappyView\'s query endpoints return raw records and procedure endpoints proxy simple creates and updates. Lua scripts let you go much further:","source":"@site/docs/guides/scripting.md","sourceDirName":"guides","slug":"/guides/scripting","permalink":"/guides/scripting","draft":false,"unlisted":false,"tags":[],"version":"current","frontMatter":{},"sidebar":"docs","previous":{"title":"Lexicons","permalink":"/guides/lexicons"},"next":{"title":"Backfill","permalink":"/guides/backfill"}}');var l=n(4848),i=n(8453);const d={},t="Lua Scripting",a={},c=[{value:"Script structure",id:"script-structure",level:2},{value:"Sandbox",id:"sandbox",level:2},{value:"Context globals",id:"context-globals",level:2},{value:"Procedure globals",id:"procedure-globals",level:3},{value:"Query globals",id:"query-globals",level:3},{value:"Utility globals",id:"utility-globals",level:2},{value:"Record API",id:"record-api",level:2},{value:"Constructor",id:"constructor",level:3},{value:"Static methods",id:"static-methods",level:3},{value:"Instance methods",id:"instance-methods",level:3},{value:"Instance fields",id:"instance-fields",level:3},{value:"Schema validation",id:"schema-validation",level:3},{value:"Save behavior",id:"save-behavior",level:3},{value:"Database API",id:"database-api",level:2},{value:"db.query",id:"dbquery",level:3},{value:"db.get",id:"dbget",level:3},{value:"db.count",id:"dbcount",level:3},{value:"Standard libraries",id:"standard-libraries",level:2},{value:"Debugging",id:"debugging",level:2},{value:"Logging",id:"logging",level:3},{value:"Error messages",id:"error-messages",level:3},{value:"Common mistakes",id:"common-mistakes",level:3},{value:"Example scripts",id:"example-scripts",level:2},{value:"Next steps",id:"next-steps",level:2}];function h(e){const r={a:"a",code:"code",h1:"h1",h2:"h2",h3:"h3",header:"header",li:"li",p:"p",pre:"pre",strong:"strong",table:"table",tbody:"tbody",td:"td",th:"th",thead:"thead",tr:"tr",ul:"ul",...(0,i.R)(),...e.components},{Details:n}=r;return n||function(e,r){throw new Error("Expected "+(r?"component":"object")+" `"+e+"` to be defined: you likely forgot to import, pass, or provide it.")}("Details",!0),(0,l.jsxs)(l.Fragment,{children:[(0,l.jsx)(r.header,{children:(0,l.jsx)(r.h1,{id:"lua-scripting",children:"Lua Scripting"})}),"\n",(0,l.jsx)(r.p,{children:"Without Lua scripts, HappyView's query endpoints return raw records and procedure endpoints proxy simple creates and updates. Lua scripts let you go much further:"}),"\n",(0,l.jsxs)(r.ul,{children:["\n",(0,l.jsx)(r.li,{children:"Add filtering logic"}),"\n",(0,l.jsx)(r.li,{children:"Transform responses"}),"\n",(0,l.jsx)(r.li,{children:"Validate input"}),"\n",(0,l.jsx)(r.li,{children:"Compose multi-record operations"}),"\n",(0,l.jsx)(r.li,{children:"Build entirely custom behavior"}),"\n"]}),"\n",(0,l.jsxs)(r.p,{children:["Scripts are attached to query and procedure lexicons and run in a sandboxed Lua VM with access to the ",(0,l.jsx)(r.a,{href:"#record-api",children:"Record API"}),", a ",(0,l.jsx)(r.a,{href:"#database-api",children:"read-only database API"}),", and a set of ",(0,l.jsx)(r.a,{href:"#context-globals",children:"context globals"}),"."]}),"\n",(0,l.jsx)(r.h2,{id:"script-structure",children:"Script structure"}),"\n",(0,l.jsxs)(r.p,{children:["Every script must define a ",(0,l.jsx)(r.code,{children:"handle()"})," function. HappyView calls it when the XRPC endpoint is hit and returns its result as JSON to the client."]}),"\n",(0,l.jsx)(r.pre,{children:(0,l.jsx)(r.code,{className:"language-lua",children:'function handle()\n -- your logic here\n return { key = "value" }\nend\n'})}),"\n",(0,l.jsxs)(r.p,{children:["You can define helper functions and variables outside ",(0,l.jsx)(r.code,{children:"handle()"}),". They're evaluated once when the script loads, then ",(0,l.jsx)(r.code,{children:"handle()"})," is called per request."]}),"\n",(0,l.jsx)(r.h2,{id:"sandbox",children:"Sandbox"}),"\n",(0,l.jsxs)(r.p,{children:["Scripts run in a restricted environment. The following standard Lua modules are ",(0,l.jsx)(r.strong,{children:"removed"})," and unavailable:"]}),"\n",(0,l.jsxs)(r.p,{children:[(0,l.jsx)(r.code,{children:"os"}),", ",(0,l.jsx)(r.code,{children:"io"}),", ",(0,l.jsx)(r.code,{children:"debug"}),", ",(0,l.jsx)(r.code,{children:"package"}),", ",(0,l.jsx)(r.code,{children:"require"}),", ",(0,l.jsx)(r.code,{children:"dofile"}),", ",(0,l.jsx)(r.code,{children:"loadfile"}),", ",(0,l.jsx)(r.code,{children:"load"}),", ",(0,l.jsx)(r.code,{children:"collectgarbage"})]}),"\n",(0,l.jsx)(r.p,{children:"An instruction limit of 1,000,000 prevents infinite loops. Exceeding it terminates the script with an error."}),"\n",(0,l.jsx)(r.h2,{id:"context-globals",children:"Context globals"}),"\n",(0,l.jsxs)(r.p,{children:["These globals are set automatically before ",(0,l.jsx)(r.code,{children:"handle()"})," is called."]}),"\n",(0,l.jsx)(r.h3,{id:"procedure-globals",children:"Procedure globals"}),"\n",(0,l.jsxs)(r.table,{children:[(0,l.jsx)(r.thead,{children:(0,l.jsxs)(r.tr,{children:[(0,l.jsx)(r.th,{children:"Global"}),(0,l.jsx)(r.th,{children:"Type"}),(0,l.jsx)(r.th,{children:"Description"})]})}),(0,l.jsxs)(r.tbody,{children:[(0,l.jsxs)(r.tr,{children:[(0,l.jsx)(r.td,{children:(0,l.jsx)(r.code,{children:"method"})}),(0,l.jsx)(r.td,{children:"string"}),(0,l.jsxs)(r.td,{children:["The XRPC method name (e.g. ",(0,l.jsx)(r.code,{children:"xyz.statusphere.setStatus"}),")"]})]}),(0,l.jsxs)(r.tr,{children:[(0,l.jsx)(r.td,{children:(0,l.jsx)(r.code,{children:"input"})}),(0,l.jsx)(r.td,{children:"table"}),(0,l.jsx)(r.td,{children:"Parsed JSON request body"})]}),(0,l.jsxs)(r.tr,{children:[(0,l.jsx)(r.td,{children:(0,l.jsx)(r.code,{children:"caller_did"})}),(0,l.jsx)(r.td,{children:"string"}),(0,l.jsx)(r.td,{children:"DID of the authenticated user"})]}),(0,l.jsxs)(r.tr,{children:[(0,l.jsx)(r.td,{children:(0,l.jsx)(r.code,{children:"collection"})}),(0,l.jsx)(r.td,{children:"string"}),(0,l.jsx)(r.td,{children:"Target collection NSID"})]})]})]}),"\n",(0,l.jsx)(r.h3,{id:"query-globals",children:"Query globals"}),"\n",(0,l.jsxs)(r.table,{children:[(0,l.jsx)(r.thead,{children:(0,l.jsxs)(r.tr,{children:[(0,l.jsx)(r.th,{children:"Global"}),(0,l.jsx)(r.th,{children:"Type"}),(0,l.jsx)(r.th,{children:"Description"})]})}),(0,l.jsxs)(r.tbody,{children:[(0,l.jsxs)(r.tr,{children:[(0,l.jsx)(r.td,{children:(0,l.jsx)(r.code,{children:"method"})}),(0,l.jsx)(r.td,{children:"string"}),(0,l.jsx)(r.td,{children:"The XRPC method name"})]}),(0,l.jsxs)(r.tr,{children:[(0,l.jsx)(r.td,{children:(0,l.jsx)(r.code,{children:"params"})}),(0,l.jsx)(r.td,{children:"table"}),(0,l.jsx)(r.td,{children:"Query string parameters (all values are strings)"})]}),(0,l.jsxs)(r.tr,{children:[(0,l.jsx)(r.td,{children:(0,l.jsx)(r.code,{children:"collection"})}),(0,l.jsx)(r.td,{children:"string"}),(0,l.jsx)(r.td,{children:"Target collection NSID"})]})]})]}),"\n",(0,l.jsxs)(r.p,{children:["Queries are unauthenticated: there is no ",(0,l.jsx)(r.code,{children:"caller_did"})," or ",(0,l.jsx)(r.code,{children:"input"}),"."]}),"\n",(0,l.jsx)(r.h2,{id:"utility-globals",children:"Utility globals"}),"\n",(0,l.jsx)(r.p,{children:"Available in both queries and procedures:"}),"\n",(0,l.jsxs)(r.table,{children:[(0,l.jsx)(r.thead,{children:(0,l.jsxs)(r.tr,{children:[(0,l.jsx)(r.th,{children:"Function"}),(0,l.jsx)(r.th,{children:"Returns"}),(0,l.jsx)(r.th,{children:"Description"})]})}),(0,l.jsxs)(r.tbody,{children:[(0,l.jsxs)(r.tr,{children:[(0,l.jsx)(r.td,{children:(0,l.jsx)(r.code,{children:"now()"})}),(0,l.jsx)(r.td,{children:"string"}),(0,l.jsx)(r.td,{children:"Current UTC timestamp in ISO 8601 format"})]}),(0,l.jsxs)(r.tr,{children:[(0,l.jsx)(r.td,{children:(0,l.jsx)(r.code,{children:"log(message)"})}),(0,l.jsx)(r.td,{children:"\u2014"}),(0,l.jsx)(r.td,{children:"Log a message (appears in server logs at debug level)"})]}),(0,l.jsxs)(r.tr,{children:[(0,l.jsx)(r.td,{children:(0,l.jsx)(r.code,{children:"TID()"})}),(0,l.jsx)(r.td,{children:"string"}),(0,l.jsx)(r.td,{children:"Generate a fresh AT Protocol TID (13-character sortable identifier)"})]})]})]}),"\n",(0,l.jsx)(r.h2,{id:"record-api",children:"Record API"}),"\n",(0,l.jsxs)(r.p,{children:["The ",(0,l.jsx)(r.code,{children:"Record"})," API is only available in ",(0,l.jsx)(r.strong,{children:"procedure"})," scripts. It handles creating, updating, loading, and deleting AT Protocol records. Writes are proxied to the caller's PDS and indexed locally."]}),"\n",(0,l.jsx)(r.h3,{id:"constructor",children:"Constructor"}),"\n",(0,l.jsx)(r.pre,{children:(0,l.jsx)(r.code,{className:"language-lua",children:'local r = Record("xyz.statusphere.status", { status = "\\ud83d\\ude0a", createdAt = now() })\n'})}),"\n",(0,l.jsxs)(r.p,{children:["Creates a new record instance for the given collection. The optional second argument sets initial field values. The record's ",(0,l.jsx)(r.code,{children:"_key_type"})," is automatically set from the lexicon's ",(0,l.jsx)(r.code,{children:"key"})," definition. Default values from the schema are populated for any missing fields."]}),"\n",(0,l.jsx)(r.h3,{id:"static-methods",children:"Static methods"}),"\n",(0,l.jsx)(r.pre,{children:(0,l.jsx)(r.code,{className:"language-lua",children:'-- Save multiple records in parallel\nRecord.save_all({ record1, record2, record3 })\n\n-- Load a record from the local database by AT URI\nlocal r = Record.load("at://did:plc:abc/xyz.statusphere.status/abc123")\n-- Returns nil if not found\n\n-- Load multiple records in parallel\nlocal records = Record.load_all({ uri1, uri2 })\n-- Returns nil entries for URIs not found\n'})}),"\n",(0,l.jsx)(r.h3,{id:"instance-methods",children:"Instance methods"}),"\n",(0,l.jsx)(r.pre,{children:(0,l.jsx)(r.code,{className:"language-lua",children:'-- Save (creates or updates depending on whether _uri is set)\nr:save()\n\n-- Delete from PDS and local database\nr:delete()\n\n-- Set the record key type (tid, any, nsid, or literal:*)\nr:set_key_type("tid")\n\n-- Set a specific record key\nr:set_rkey("my-key")\n\n-- Auto-generate a record key based on _key_type\nlocal key = r:generate_rkey()\n'})}),"\n",(0,l.jsx)(r.p,{children:(0,l.jsxs)(r.strong,{children:["Key type behavior for ",(0,l.jsx)(r.code,{children:"generate_rkey()"}),":"]})}),"\n",(0,l.jsxs)(r.table,{children:[(0,l.jsx)(r.thead,{children:(0,l.jsxs)(r.tr,{children:[(0,l.jsx)(r.th,{children:"Key type"}),(0,l.jsx)(r.th,{children:"Generated rkey"})]})}),(0,l.jsxs)(r.tbody,{children:[(0,l.jsxs)(r.tr,{children:[(0,l.jsx)(r.td,{children:(0,l.jsx)(r.code,{children:"tid"})}),(0,l.jsx)(r.td,{children:"Sortable timestamp-based ID"})]}),(0,l.jsxs)(r.tr,{children:[(0,l.jsx)(r.td,{children:(0,l.jsx)(r.code,{children:"any"})}),(0,l.jsxs)(r.td,{children:["Same as ",(0,l.jsx)(r.code,{children:"tid"})]})]}),(0,l.jsxs)(r.tr,{children:[(0,l.jsx)(r.td,{children:(0,l.jsx)(r.code,{children:"literal:value"})}),(0,l.jsx)(r.td,{children:"The literal value after the colon"})]}),(0,l.jsxs)(r.tr,{children:[(0,l.jsx)(r.td,{children:(0,l.jsx)(r.code,{children:"nsid"})}),(0,l.jsxs)(r.td,{children:["Error \u2014 use ",(0,l.jsx)(r.code,{children:"set_rkey()"})," instead"]})]})]})]}),"\n",(0,l.jsx)(r.h3,{id:"instance-fields",children:"Instance fields"}),"\n",(0,l.jsx)(r.p,{children:"These fields are set automatically and are read-only (writes raise an error):"}),"\n",(0,l.jsxs)(r.table,{children:[(0,l.jsx)(r.thead,{children:(0,l.jsxs)(r.tr,{children:[(0,l.jsx)(r.th,{children:"Field"}),(0,l.jsx)(r.th,{children:"Type"}),(0,l.jsx)(r.th,{children:"Description"})]})}),(0,l.jsxs)(r.tbody,{children:[(0,l.jsxs)(r.tr,{children:[(0,l.jsx)(r.td,{children:(0,l.jsx)(r.code,{children:"_uri"})}),(0,l.jsx)(r.td,{children:"string?"}),(0,l.jsxs)(r.td,{children:["AT URI \u2014 set after ",(0,l.jsx)(r.code,{children:"save()"}),", cleared after ",(0,l.jsx)(r.code,{children:"delete()"})]})]}),(0,l.jsxs)(r.tr,{children:[(0,l.jsx)(r.td,{children:(0,l.jsx)(r.code,{children:"_cid"})}),(0,l.jsx)(r.td,{children:"string?"}),(0,l.jsxs)(r.td,{children:["Content hash \u2014 set after ",(0,l.jsx)(r.code,{children:"save()"}),", cleared after ",(0,l.jsx)(r.code,{children:"delete()"})]})]}),(0,l.jsxs)(r.tr,{children:[(0,l.jsx)(r.td,{children:(0,l.jsx)(r.code,{children:"_key_type"})}),(0,l.jsx)(r.td,{children:"string?"}),(0,l.jsx)(r.td,{children:"Record key type from the lexicon definition"})]}),(0,l.jsxs)(r.tr,{children:[(0,l.jsx)(r.td,{children:(0,l.jsx)(r.code,{children:"_rkey"})}),(0,l.jsx)(r.td,{children:"string?"}),(0,l.jsxs)(r.td,{children:["Record key \u2014 set via ",(0,l.jsx)(r.code,{children:"set_rkey()"})," or ",(0,l.jsx)(r.code,{children:"generate_rkey()"})]})]}),(0,l.jsxs)(r.tr,{children:[(0,l.jsx)(r.td,{children:(0,l.jsx)(r.code,{children:"_collection"})}),(0,l.jsx)(r.td,{children:"string"}),(0,l.jsx)(r.td,{children:"Collection NSID (always set)"})]}),(0,l.jsxs)(r.tr,{children:[(0,l.jsx)(r.td,{children:(0,l.jsx)(r.code,{children:"_schema"})}),(0,l.jsx)(r.td,{children:"table?"}),(0,l.jsx)(r.td,{children:"Schema definition from the lexicon (used for validation)"})]})]})]}),"\n",(0,l.jsx)(r.h3,{id:"schema-validation",children:"Schema validation"}),"\n",(0,l.jsx)(r.p,{children:"When a record has a schema (loaded from the lexicon):"}),"\n",(0,l.jsxs)(r.ul,{children:["\n",(0,l.jsxs)(r.li,{children:[(0,l.jsx)(r.strong,{children:"On save:"})," required fields are checked, and missing required fields raise an error"]}),"\n",(0,l.jsxs)(r.li,{children:[(0,l.jsx)(r.strong,{children:"On construction:"})," default values from schema properties are auto-populated"]}),"\n",(0,l.jsxs)(r.li,{children:[(0,l.jsx)(r.strong,{children:"On save:"})," only fields defined in the schema's ",(0,l.jsx)(r.code,{children:"properties"})," are sent to the PDS"]}),"\n"]}),"\n",(0,l.jsx)(r.h3,{id:"save-behavior",children:"Save behavior"}),"\n",(0,l.jsxs)(r.p,{children:[(0,l.jsx)(r.code,{children:"r:save()"})," auto-detects create vs update:"]}),"\n",(0,l.jsxs)(r.ul,{children:["\n",(0,l.jsxs)(r.li,{children:["If ",(0,l.jsx)(r.code,{children:"_uri"})," is nil \u2192 calls ",(0,l.jsx)(r.code,{children:"createRecord"})," on the PDS"]}),"\n",(0,l.jsxs)(r.li,{children:["If ",(0,l.jsx)(r.code,{children:"_uri"})," is set \u2192 calls ",(0,l.jsx)(r.code,{children:"putRecord"})," on the PDS"]}),"\n"]}),"\n",(0,l.jsxs)(r.p,{children:["After a successful save, ",(0,l.jsx)(r.code,{children:"_uri"})," and ",(0,l.jsx)(r.code,{children:"_cid"})," are updated on the record instance."]}),"\n",(0,l.jsx)(r.h2,{id:"database-api",children:"Database API"}),"\n",(0,l.jsxs)(r.p,{children:["The ",(0,l.jsx)(r.code,{children:"db"})," table provides read-only access to indexed records. Available in both queries and procedures."]}),"\n",(0,l.jsx)(r.h3,{id:"dbquery",children:"db.query"}),"\n",(0,l.jsx)(r.pre,{children:(0,l.jsx)(r.code,{className:"language-lua",children:'local result = db.query({\n collection = "xyz.statusphere.status", -- required\n did = "did:plc:abc", -- optional: filter by DID\n limit = 20, -- optional: max 100, default 20\n offset = 0, -- optional: for pagination\n})\n\n-- result.records \u2014 array of record tables (each includes a "uri" field)\n-- result.cursor \u2014 present when more records exist\n'})}),"\n",(0,l.jsx)(r.h3,{id:"dbget",children:"db.get"}),"\n",(0,l.jsx)(r.pre,{children:(0,l.jsx)(r.code,{className:"language-lua",children:'local record = db.get("at://did:plc:abc/xyz.statusphere.status/abc123")\n-- Returns the record table or nil\n-- The returned table includes a "uri" field\n'})}),"\n",(0,l.jsx)(r.h3,{id:"dbcount",children:"db.count"}),"\n",(0,l.jsx)(r.pre,{children:(0,l.jsx)(r.code,{className:"language-lua",children:'local n = db.count("xyz.statusphere.status")\nlocal n = db.count("xyz.statusphere.status", "did:plc:abc") -- filter by DID\n'})}),"\n",(0,l.jsx)(r.h2,{id:"standard-libraries",children:"Standard libraries"}),"\n",(0,l.jsx)(r.p,{children:"The following Lua 5.4 standard library modules are available:"}),"\n",(0,l.jsxs)(n,{children:[(0,l.jsx)("summary",{children:(0,l.jsx)(r.p,{children:(0,l.jsx)(r.code,{children:"string"})})}),(0,l.jsxs)(r.ul,{children:["\n",(0,l.jsx)(r.li,{children:(0,l.jsx)(r.a,{href:"https://lua.org/manual/5.4/manual.html#pdf-string.byte",children:(0,l.jsx)(r.code,{children:"byte"})})}),"\n",(0,l.jsx)(r.li,{children:(0,l.jsx)(r.a,{href:"https://lua.org/manual/5.4/manual.html#pdf-string.char",children:(0,l.jsx)(r.code,{children:"char"})})}),"\n",(0,l.jsx)(r.li,{children:(0,l.jsx)(r.a,{href:"https://lua.org/manual/5.4/manual.html#pdf-string.find",children:(0,l.jsx)(r.code,{children:"find"})})}),"\n",(0,l.jsx)(r.li,{children:(0,l.jsx)(r.a,{href:"https://lua.org/manual/5.4/manual.html#pdf-string.format",children:(0,l.jsx)(r.code,{children:"format"})})}),"\n",(0,l.jsx)(r.li,{children:(0,l.jsx)(r.a,{href:"https://lua.org/manual/5.4/manual.html#pdf-string.gmatch",children:(0,l.jsx)(r.code,{children:"gmatch"})})}),"\n",(0,l.jsx)(r.li,{children:(0,l.jsx)(r.a,{href:"https://lua.org/manual/5.4/manual.html#pdf-string.gsub",children:(0,l.jsx)(r.code,{children:"gsub"})})}),"\n",(0,l.jsx)(r.li,{children:(0,l.jsx)(r.a,{href:"https://lua.org/manual/5.4/manual.html#pdf-string.len",children:(0,l.jsx)(r.code,{children:"len"})})}),"\n",(0,l.jsx)(r.li,{children:(0,l.jsx)(r.a,{href:"https://lua.org/manual/5.4/manual.html#pdf-string.lower",children:(0,l.jsx)(r.code,{children:"lower"})})}),"\n",(0,l.jsx)(r.li,{children:(0,l.jsx)(r.a,{href:"https://lua.org/manual/5.4/manual.html#pdf-string.match",children:(0,l.jsx)(r.code,{children:"match"})})}),"\n",(0,l.jsx)(r.li,{children:(0,l.jsx)(r.a,{href:"https://lua.org/manual/5.4/manual.html#pdf-string.rep",children:(0,l.jsx)(r.code,{children:"rep"})})}),"\n",(0,l.jsx)(r.li,{children:(0,l.jsx)(r.a,{href:"https://lua.org/manual/5.4/manual.html#pdf-string.reverse",children:(0,l.jsx)(r.code,{children:"reverse"})})}),"\n",(0,l.jsx)(r.li,{children:(0,l.jsx)(r.a,{href:"https://lua.org/manual/5.4/manual.html#pdf-string.sub",children:(0,l.jsx)(r.code,{children:"sub"})})}),"\n",(0,l.jsx)(r.li,{children:(0,l.jsx)(r.a,{href:"https://lua.org/manual/5.4/manual.html#pdf-string.upper",children:(0,l.jsx)(r.code,{children:"upper"})})}),"\n"]})]}),"\n",(0,l.jsxs)(n,{children:[(0,l.jsx)("summary",{children:(0,l.jsx)(r.p,{children:(0,l.jsx)(r.code,{children:"table"})})}),(0,l.jsxs)(r.ul,{children:["\n",(0,l.jsx)(r.li,{children:(0,l.jsx)(r.a,{href:"https://lua.org/manual/5.4/manual.html#pdf-table.concat",children:(0,l.jsx)(r.code,{children:"concat"})})}),"\n",(0,l.jsx)(r.li,{children:(0,l.jsx)(r.a,{href:"https://lua.org/manual/5.4/manual.html#pdf-table.insert",children:(0,l.jsx)(r.code,{children:"insert"})})}),"\n",(0,l.jsx)(r.li,{children:(0,l.jsx)(r.a,{href:"https://lua.org/manual/5.4/manual.html#pdf-table.remove",children:(0,l.jsx)(r.code,{children:"remove"})})}),"\n",(0,l.jsx)(r.li,{children:(0,l.jsx)(r.a,{href:"https://lua.org/manual/5.4/manual.html#pdf-table.sort",children:(0,l.jsx)(r.code,{children:"sort"})})}),"\n",(0,l.jsx)(r.li,{children:(0,l.jsx)(r.a,{href:"https://lua.org/manual/5.4/manual.html#pdf-table.unpack",children:(0,l.jsx)(r.code,{children:"unpack"})})}),"\n"]})]}),"\n",(0,l.jsxs)(n,{children:[(0,l.jsx)("summary",{children:(0,l.jsx)(r.p,{children:(0,l.jsx)(r.code,{children:"math"})})}),(0,l.jsxs)(r.ul,{children:["\n",(0,l.jsx)(r.li,{children:(0,l.jsx)(r.a,{href:"https://lua.org/manual/5.4/manual.html#pdf-math.abs",children:(0,l.jsx)(r.code,{children:"abs"})})}),"\n",(0,l.jsx)(r.li,{children:(0,l.jsx)(r.a,{href:"https://lua.org/manual/5.4/manual.html#pdf-math.ceil",children:(0,l.jsx)(r.code,{children:"ceil"})})}),"\n",(0,l.jsx)(r.li,{children:(0,l.jsx)(r.a,{href:"https://lua.org/manual/5.4/manual.html#pdf-math.floor",children:(0,l.jsx)(r.code,{children:"floor"})})}),"\n",(0,l.jsx)(r.li,{children:(0,l.jsx)(r.a,{href:"https://lua.org/manual/5.4/manual.html#pdf-math.max",children:(0,l.jsx)(r.code,{children:"max"})})}),"\n",(0,l.jsx)(r.li,{children:(0,l.jsx)(r.a,{href:"https://lua.org/manual/5.4/manual.html#pdf-math.min",children:(0,l.jsx)(r.code,{children:"min"})})}),"\n",(0,l.jsx)(r.li,{children:(0,l.jsx)(r.a,{href:"https://lua.org/manual/5.4/manual.html#pdf-math.random",children:(0,l.jsx)(r.code,{children:"random"})})}),"\n",(0,l.jsx)(r.li,{children:(0,l.jsx)(r.a,{href:"https://lua.org/manual/5.4/manual.html#pdf-math.sqrt",children:(0,l.jsx)(r.code,{children:"sqrt"})})}),"\n",(0,l.jsx)(r.li,{children:(0,l.jsx)(r.a,{href:"https://lua.org/manual/5.4/manual.html#pdf-math.huge",children:(0,l.jsx)(r.code,{children:"huge"})})}),"\n",(0,l.jsx)(r.li,{children:(0,l.jsx)(r.a,{href:"https://lua.org/manual/5.4/manual.html#pdf-math.pi",children:(0,l.jsx)(r.code,{children:"pi"})})}),"\n"]})]}),"\n",(0,l.jsxs)(n,{children:[(0,l.jsx)("summary",{children:(0,l.jsx)(r.p,{children:"Standard builtins"})}),(0,l.jsxs)(r.ul,{children:["\n",(0,l.jsx)(r.li,{children:(0,l.jsx)(r.a,{href:"https://lua.org/manual/5.4/manual.html#pdf-print",children:(0,l.jsx)(r.code,{children:"print"})})}),"\n",(0,l.jsx)(r.li,{children:(0,l.jsx)(r.a,{href:"https://lua.org/manual/5.4/manual.html#pdf-tostring",children:(0,l.jsx)(r.code,{children:"tostring"})})}),"\n",(0,l.jsx)(r.li,{children:(0,l.jsx)(r.a,{href:"https://lua.org/manual/5.4/manual.html#pdf-tonumber",children:(0,l.jsx)(r.code,{children:"tonumber"})})}),"\n",(0,l.jsx)(r.li,{children:(0,l.jsx)(r.a,{href:"https://lua.org/manual/5.4/manual.html#pdf-type",children:(0,l.jsx)(r.code,{children:"type"})})}),"\n",(0,l.jsx)(r.li,{children:(0,l.jsx)(r.a,{href:"https://lua.org/manual/5.4/manual.html#pdf-pairs",children:(0,l.jsx)(r.code,{children:"pairs"})})}),"\n",(0,l.jsx)(r.li,{children:(0,l.jsx)(r.a,{href:"https://lua.org/manual/5.4/manual.html#pdf-ipairs",children:(0,l.jsx)(r.code,{children:"ipairs"})})}),"\n",(0,l.jsx)(r.li,{children:(0,l.jsx)(r.a,{href:"https://lua.org/manual/5.4/manual.html#pdf-next",children:(0,l.jsx)(r.code,{children:"next"})})}),"\n",(0,l.jsx)(r.li,{children:(0,l.jsx)(r.a,{href:"https://lua.org/manual/5.4/manual.html#pdf-select",children:(0,l.jsx)(r.code,{children:"select"})})}),"\n",(0,l.jsx)(r.li,{children:(0,l.jsx)(r.a,{href:"https://lua.org/manual/5.4/manual.html#pdf-table.unpack",children:(0,l.jsx)(r.code,{children:"unpack"})})}),"\n",(0,l.jsx)(r.li,{children:(0,l.jsx)(r.a,{href:"https://lua.org/manual/5.4/manual.html#pdf-error",children:(0,l.jsx)(r.code,{children:"error"})})}),"\n",(0,l.jsx)(r.li,{children:(0,l.jsx)(r.a,{href:"https://lua.org/manual/5.4/manual.html#pdf-pcall",children:(0,l.jsx)(r.code,{children:"pcall"})})}),"\n",(0,l.jsx)(r.li,{children:(0,l.jsx)(r.a,{href:"https://lua.org/manual/5.4/manual.html#pdf-xpcall",children:(0,l.jsx)(r.code,{children:"xpcall"})})}),"\n",(0,l.jsx)(r.li,{children:(0,l.jsx)(r.a,{href:"https://lua.org/manual/5.4/manual.html#pdf-assert",children:(0,l.jsx)(r.code,{children:"assert"})})}),"\n",(0,l.jsx)(r.li,{children:(0,l.jsx)(r.a,{href:"https://lua.org/manual/5.4/manual.html#pdf-setmetatable",children:(0,l.jsx)(r.code,{children:"setmetatable"})})}),"\n",(0,l.jsx)(r.li,{children:(0,l.jsx)(r.a,{href:"https://lua.org/manual/5.4/manual.html#pdf-getmetatable",children:(0,l.jsx)(r.code,{children:"getmetatable"})})}),"\n",(0,l.jsx)(r.li,{children:(0,l.jsx)(r.a,{href:"https://lua.org/manual/5.4/manual.html#pdf-rawget",children:(0,l.jsx)(r.code,{children:"rawget"})})}),"\n",(0,l.jsx)(r.li,{children:(0,l.jsx)(r.a,{href:"https://lua.org/manual/5.4/manual.html#pdf-rawset",children:(0,l.jsx)(r.code,{children:"rawset"})})}),"\n",(0,l.jsx)(r.li,{children:(0,l.jsx)(r.a,{href:"https://lua.org/manual/5.4/manual.html#pdf-rawequal",children:(0,l.jsx)(r.code,{children:"rawequal"})})}),"\n"]})]}),"\n",(0,l.jsx)(r.h2,{id:"debugging",children:"Debugging"}),"\n",(0,l.jsx)(r.h3,{id:"logging",children:"Logging"}),"\n",(0,l.jsxs)(r.p,{children:["Use ",(0,l.jsx)(r.code,{children:"log()"})," to trace script execution. Output appears in the server logs at ",(0,l.jsx)(r.strong,{children:"debug"})," level with the field ",(0,l.jsx)(r.code,{children:"lua_log"}),":"]}),"\n",(0,l.jsx)(r.pre,{children:(0,l.jsx)(r.code,{className:"language-lua",children:'function handle()\n log("handle called with params: " .. tostring(params.limit))\n local result = db.query({ collection = collection, limit = params.limit })\n log("query returned " .. #result.records .. " records")\n return result\nend\n'})}),"\n",(0,l.jsxs)(r.p,{children:["To see log output, make sure your ",(0,l.jsx)(r.code,{children:"RUST_LOG"})," environment variable includes debug level for HappyView (the default ",(0,l.jsx)(r.code,{children:"happyview=debug"})," works). See ",(0,l.jsx)(r.a,{href:"/getting-started/configuration",children:"Configuration"}),"."]}),"\n",(0,l.jsx)(r.h3,{id:"error-messages",children:"Error messages"}),"\n",(0,l.jsxs)(r.p,{children:["When a script fails, the client receives a generic ",(0,l.jsx)(r.code,{children:"500"})," response:"]}),"\n",(0,l.jsxs)(r.ul,{children:["\n",(0,l.jsxs)(r.li,{children:[(0,l.jsx)(r.code,{children:'{"error": "script execution failed"}'}),": covers syntax errors, runtime errors, missing ",(0,l.jsx)(r.code,{children:"handle()"})," function, and errors raised with ",(0,l.jsx)(r.code,{children:"error()"})]}),"\n",(0,l.jsxs)(r.li,{children:[(0,l.jsx)(r.code,{children:'{"error": "script exceeded execution time limit"}'}),": the script hit the 1,000,000 instruction limit"]}),"\n"]}),"\n",(0,l.jsxs)(r.p,{children:["The ",(0,l.jsx)(r.strong,{children:"full error message"})," is logged server-side at error level. Check the server logs to see the actual Lua error, including line numbers and stack traces."]}),"\n",(0,l.jsx)(r.h3,{id:"common-mistakes",children:"Common mistakes"}),"\n",(0,l.jsxs)(r.ul,{children:["\n",(0,l.jsxs)(r.li,{children:[(0,l.jsxs)(r.strong,{children:["Missing ",(0,l.jsx)(r.code,{children:"handle()"})," function"]}),": Every script must define a global ",(0,l.jsx)(r.code,{children:"handle()"}),' function. If it\'s missing or misspelled, the script fails silently with "script execution failed".']}),"\n",(0,l.jsxs)(r.li,{children:[(0,l.jsxs)(r.strong,{children:["Calling ",(0,l.jsx)(r.code,{children:"error()"})," for expected conditions"]}),": Lua's ",(0,l.jsx)(r.code,{children:"error()"}),' triggers a 500 response. For expected conditions like "record not found", return a structured error response instead: ',(0,l.jsx)(r.code,{children:'return { error = "not found" }'}),"."]}),"\n",(0,l.jsxs)(r.li,{children:[(0,l.jsx)(r.strong,{children:"Infinite loops"}),": The sandbox enforces a 1,000,000 instruction limit. If your script processes large data sets, paginate with ",(0,l.jsx)(r.code,{children:"db.query()"})," limits instead of loading everything at once."]}),"\n",(0,l.jsxs)(r.li,{children:[(0,l.jsxs)(r.strong,{children:["Forgetting ",(0,l.jsx)(r.code,{children:"params"})," values are strings"]}),": All query string parameters arrive as strings. Use ",(0,l.jsx)(r.code,{children:"tonumber(params.limit)"})," if you need a number."]}),"\n"]}),"\n",(0,l.jsx)(r.h2,{id:"example-scripts",children:"Example scripts"}),"\n",(0,l.jsx)(r.p,{children:"See the example script references for complete, ready-to-use scripts:"}),"\n",(0,l.jsx)(r.p,{children:(0,l.jsx)(r.strong,{children:"Queries:"})}),"\n",(0,l.jsxs)(r.ul,{children:["\n",(0,l.jsxs)(r.li,{children:[(0,l.jsx)(r.a,{href:"/reference/scripts/get-record",children:"Get a record"})," \u2014 fetch a single record by AT URI"]}),"\n",(0,l.jsxs)(r.li,{children:[(0,l.jsx)(r.a,{href:"/reference/scripts/paginated-list",children:"Paginated list"})," \u2014 list records with cursor-based pagination and DID filtering"]}),"\n",(0,l.jsxs)(r.li,{children:[(0,l.jsx)(r.a,{href:"/reference/scripts/list-or-fetch",children:"List or fetch"})," \u2014 combined single-record lookup and paginated listing"]}),"\n",(0,l.jsxs)(r.li,{children:[(0,l.jsx)(r.a,{href:"/reference/scripts/expanded-query",children:"Expanded query"})," \u2014 list statuses with user profiles in a single response"]}),"\n"]}),"\n",(0,l.jsx)(r.p,{children:(0,l.jsx)(r.strong,{children:"Procedures:"})}),"\n",(0,l.jsxs)(r.ul,{children:["\n",(0,l.jsxs)(r.li,{children:[(0,l.jsx)(r.a,{href:"/reference/scripts/create-record",children:"Create a record"})," \u2014 simple write that saves input as a record"]}),"\n",(0,l.jsxs)(r.li,{children:[(0,l.jsx)(r.a,{href:"/reference/scripts/upsert-record",children:"Upsert a record"})," \u2014 create or update using a deterministic rkey"]}),"\n",(0,l.jsxs)(r.li,{children:[(0,l.jsx)(r.a,{href:"/reference/scripts/update-or-delete",children:"Update or delete"})," \u2014 single endpoint handling create, update, and delete"]}),"\n",(0,l.jsxs)(r.li,{children:[(0,l.jsx)(r.a,{href:"/reference/scripts/batch-save",children:"Batch save"})," \u2014 create multiple records in parallel with ",(0,l.jsx)(r.code,{children:"Record.save_all()"})]}),"\n",(0,l.jsxs)(r.li,{children:[(0,l.jsx)(r.a,{href:"/reference/scripts/sidecar-records",children:"Sidecar records"})," \u2014 create linked records across collections with a shared rkey"]}),"\n",(0,l.jsxs)(r.li,{children:[(0,l.jsx)(r.a,{href:"/reference/scripts/cascading-delete",children:"Cascading delete"})," \u2014 delete a record and all related records"]}),"\n",(0,l.jsxs)(r.li,{children:[(0,l.jsx)(r.a,{href:"/reference/scripts/complex-mutations",children:"Complex mutations"})," \u2014 load, transform, and save a record with multiple field changes"]}),"\n"]}),"\n",(0,l.jsx)(r.h2,{id:"next-steps",children:"Next steps"}),"\n",(0,l.jsxs)(r.ul,{children:["\n",(0,l.jsxs)(r.li,{children:[(0,l.jsx)(r.a,{href:"/guides/lexicons",children:"Lexicons"}),": Understand how record, query, and procedure lexicons work together"]}),"\n",(0,l.jsxs)(r.li,{children:[(0,l.jsx)(r.a,{href:"/reference/xrpc-api",children:"XRPC API"}),": See how endpoints behave with and without Lua scripts"]}),"\n",(0,l.jsxs)(r.li,{children:[(0,l.jsx)(r.a,{href:"/getting-started/dashboard#lua-editor",children:"Dashboard"}),": Use the web editor with context-aware completions"]}),"\n"]})]})}function o(e={}){const{wrapper:r}={...(0,i.R)(),...e.components};return r?(0,l.jsx)(r,{...e,children:(0,l.jsx)(h,{...e})}):h(e)}},8453(e,r,n){n.d(r,{R:()=>d,x:()=>t});var s=n(6540);const l={},i=s.createContext(l);function d(e){const r=s.useContext(i);return s.useMemo(function(){return"function"==typeof e?e(r):{...r,...e}},[r,e])}function t(e){let r;return r=e.disableParentContext?"function"==typeof e.components?e.components(l):e.components||l:d(e.components),s.createElement(i.Provider,{value:r},e.children)}}}]); \ No newline at end of file diff --git a/assets/js/runtime~main.39d82192.js b/assets/js/runtime~main.39d82192.js new file mode 100644 --- /dev/null +++ b/assets/js/runtime~main.39d82192.js @@ -0,0 +1,1 @@ +(()=>{"use strict";var e,a,t,r,d,c={},f={};function b(e){var a=f[e];if(void 0!==a)return a.exports;var t=f[e]={exports:{}};return c[e].call(t.exports,t,t.exports,b),t.exports}b.m=c,e=[],b.O=(a,t,r,d)=>{if(!t){var c=1/0;for(i=0;i=d)&&Object.keys(b.O).every(e=>b.O[e](t[o]))?t.splice(o--,1):(f=!1,d0&&e[i-1][2]>d;i--)e[i]=e[i-1];e[i]=[t,r,d]},b.n=e=>{var a=e&&e.__esModule?()=>e.default:()=>e;return b.d(a,{a:a}),a},t=Object.getPrototypeOf?e=>Object.getPrototypeOf(e):e=>e.__proto__,b.t=function(e,r){if(1&r&&(e=this(e)),8&r)return e;if("object"==typeof e&&e){if(4&r&&e.__esModule)return e;if(16&r&&"function"==typeof e.then)return e}var d=Object.create(null);b.r(d);var c={};a=a||[null,t({}),t([]),t(t)];for(var f=2&r&&e;("object"==typeof f||"function"==typeof f)&&!~a.indexOf(f);f=t(f))Object.getOwnPropertyNames(f).forEach(a=>c[a]=()=>e[a]);return c.default=()=>e,b.d(d,c),d},b.d=(e,a)=>{for(var t in a)b.o(a,t)&&!b.o(e,t)&&Object.defineProperty(e,t,{enumerable:!0,get:a[t]})},b.f={},b.e=e=>Promise.all(Object.keys(b.f).reduce((a,t)=>(b.f[t](e,a),a),[])),b.u=e=>"assets/js/"+({20:"919abb94",203:"9985d6d5",787:"280f48ec",1361:"fbd7a87c",1567:"22dd74f7",2007:"c985d368",2278:"23374ca6",2431:"bccc5e9b",2618:"b27c7406",3197:"5d3a5990",3993:"d2e94323",4440:"5fd2dbdb",5011:"121e86a6",5563:"539fb2af",5742:"aba21aa0",5953:"920ba7ef",5986:"fac68122",6087:"a77ba7fa",6438:"bde15bec",6495:"6acefe75",6590:"43880f7a",6803:"33ec3d65",7098:"a7bd4aaa",7194:"a4086a70",7520:"89877b86",7715:"009f1e98",8401:"17896441",8779:"111eee19",8897:"a51dfc20",9048:"a94703ab",9178:"e8644baa",9492:"846656e5",9647:"5e95c892",9659:"ec125a10",9957:"f142c65c"}[e]||e)+"."+{20:"3ec12698",165:"4c5708a1",203:"2071e97d",225:"04246537",291:"e48169fe",787:"8ce2f590",1203:"4c8aeaab",1361:"e3e74927",1567:"6d79a20e",1741:"e6304f1e",1746:"afdbc2a0",1903:"c19e32df",2007:"9be5144d",2130:"6aca0963",2217:"7fbe9dae",2237:"02886161",2278:"0bc9e136",2279:"14e9bbca",2291:"742b3f1e",2334:"0d54c30f",2431:"bfceefed",2492:"04a5c13d",2618:"d8754146",2821:"25ddbeb7",3197:"a75f4a7b",3356:"916f8094",3624:"3861afa5",3815:"62684417",3993:"9cf23f81",4312:"3fc60254",4440:"3f63380d",4616:"c38d0c4b",4732:"37c818cd",4802:"c2bb0f45",4981:"a26c6f29",5011:"4a23b8e6",5149:"dc032f86",5480:"0ac51d82",5563:"ab94867b",5734:"a0ed7a40",5742:"dfe711f3",5953:"9eb561b1",5955:"5524a460",5986:"66f4e9f8",5996:"973aad9b",6087:"be66bc37",6241:"ddb16fb6",6438:"7425f799",6495:"bf671ca4",6567:"ba0bae2c",6590:"a0c8f815",6803:"9c357e99",6992:"6dfc4d99",7098:"1619d051",7194:"58f7d6fe",7520:"b544ad88",7592:"a68b31ac",7715:"833b5436",7873:"c42f29b1",7928:"08dd813c",8142:"f23a31c7",8249:"c71477f6",8401:"0e3115cb",8731:"da0fb09f",8756:"2c2f045b",8779:"94292821",8795:"1c60dc80",8897:"1b4b1274",9032:"6c8ca03c",9048:"df97c324",9178:"350e6ac9",9412:"2ce5e84d",9492:"ec5f11ac",9510:"2071eba3",9620:"117206fb",9647:"ac259cea",9659:"81999196",9717:"bdeb521f",9957:"366ae717"}[e]+".js",b.miniCssF=e=>{},b.g=function(){if("object"==typeof globalThis)return globalThis;try{return this||new Function("return this")()}catch(e){if("object"==typeof window)return window}}(),b.o=(e,a)=>Object.prototype.hasOwnProperty.call(e,a),r={},d="happyview-docs:",b.l=(e,a,t,c)=>{if(r[e])r[e].push(a);else{var f,o;if(void 0!==t)for(var n=document.getElementsByTagName("script"),i=0;i{f.onerror=f.onload=null,clearTimeout(s);var d=r[e];if(delete r[e],f.parentNode&&f.parentNode.removeChild(f),d&&d.forEach(e=>e(t)),a)return a(t)},s=setTimeout(l.bind(null,void 0,{type:"timeout",target:f}),12e4);f.onerror=l.bind(null,f.onerror),f.onload=l.bind(null,f.onload),o&&document.head.appendChild(f)}},b.r=e=>{"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},b.p="/",b.gca=function(e){return e={17896441:"8401","919abb94":"20","9985d6d5":"203","280f48ec":"787",fbd7a87c:"1361","22dd74f7":"1567",c985d368:"2007","23374ca6":"2278",bccc5e9b:"2431",b27c7406:"2618","5d3a5990":"3197",d2e94323:"3993","5fd2dbdb":"4440","121e86a6":"5011","539fb2af":"5563",aba21aa0:"5742","920ba7ef":"5953",fac68122:"5986",a77ba7fa:"6087",bde15bec:"6438","6acefe75":"6495","43880f7a":"6590","33ec3d65":"6803",a7bd4aaa:"7098",a4086a70:"7194","89877b86":"7520","009f1e98":"7715","111eee19":"8779",a51dfc20:"8897",a94703ab:"9048",e8644baa:"9178","846656e5":"9492","5e95c892":"9647",ec125a10:"9659",f142c65c:"9957"}[e]||e,b.p+b.u(e)},(()=>{var e={5354:0,1869:0};b.f.j=(a,t)=>{var r=b.o(e,a)?e[a]:void 0;if(0!==r)if(r)t.push(r[2]);else if(/^(1869|5354)$/.test(a))e[a]=0;else{var d=new Promise((t,d)=>r=e[a]=[t,d]);t.push(r[2]=d);var c=b.p+b.u(a),f=new Error;b.l(c,t=>{if(b.o(e,a)&&(0!==(r=e[a])&&(e[a]=void 0),r)){var d=t&&("load"===t.type?"missing":t.type),c=t&&t.target&&t.target.src;f.message="Loading chunk "+a+" failed.\n("+d+": "+c+")",f.name="ChunkLoadError",f.type=d,f.request=c,r[1](f)}},"chunk-"+a,a)}},b.O.j=a=>0===e[a];var a=(a,t)=>{var r,d,[c,f,o]=t,n=0;if(c.some(a=>0!==e[a])){for(r in f)b.o(f,r)&&(b.m[r]=f[r]);if(o)var i=o(b)}for(a&&a(t);n{"use strict";var e,a,t,r,d,c={},f={};function b(e){var a=f[e];if(void 0!==a)return a.exports;var t=f[e]={exports:{}};return c[e].call(t.exports,t,t.exports,b),t.exports}b.m=c,e=[],b.O=(a,t,r,d)=>{if(!t){var c=1/0;for(i=0;i=d)&&Object.keys(b.O).every(e=>b.O[e](t[o]))?t.splice(o--,1):(f=!1,d0&&e[i-1][2]>d;i--)e[i]=e[i-1];e[i]=[t,r,d]},b.n=e=>{var a=e&&e.__esModule?()=>e.default:()=>e;return b.d(a,{a:a}),a},t=Object.getPrototypeOf?e=>Object.getPrototypeOf(e):e=>e.__proto__,b.t=function(e,r){if(1&r&&(e=this(e)),8&r)return e;if("object"==typeof e&&e){if(4&r&&e.__esModule)return e;if(16&r&&"function"==typeof e.then)return e}var d=Object.create(null);b.r(d);var c={};a=a||[null,t({}),t([]),t(t)];for(var f=2&r&&e;("object"==typeof f||"function"==typeof f)&&!~a.indexOf(f);f=t(f))Object.getOwnPropertyNames(f).forEach(a=>c[a]=()=>e[a]);return c.default=()=>e,b.d(d,c),d},b.d=(e,a)=>{for(var t in a)b.o(a,t)&&!b.o(e,t)&&Object.defineProperty(e,t,{enumerable:!0,get:a[t]})},b.f={},b.e=e=>Promise.all(Object.keys(b.f).reduce((a,t)=>(b.f[t](e,a),a),[])),b.u=e=>"assets/js/"+({20:"919abb94",203:"9985d6d5",787:"280f48ec",1361:"fbd7a87c",1567:"22dd74f7",2007:"c985d368",2278:"23374ca6",2431:"bccc5e9b",2618:"b27c7406",3197:"5d3a5990",3993:"d2e94323",4440:"5fd2dbdb",5011:"121e86a6",5563:"539fb2af",5742:"aba21aa0",5953:"920ba7ef",5986:"fac68122",6087:"a77ba7fa",6438:"bde15bec",6495:"6acefe75",6590:"43880f7a",6803:"33ec3d65",7098:"a7bd4aaa",7194:"a4086a70",7520:"89877b86",7715:"009f1e98",8401:"17896441",8779:"111eee19",8897:"a51dfc20",9048:"a94703ab",9178:"e8644baa",9492:"846656e5",9647:"5e95c892",9659:"ec125a10",9957:"f142c65c"}[e]||e)+"."+{20:"3ec12698",165:"4c5708a1",203:"2071e97d",225:"04246537",291:"e48169fe",787:"8ce2f590",1203:"4c8aeaab",1361:"e3e74927",1567:"6d79a20e",1741:"e6304f1e",1746:"afdbc2a0",1903:"c19e32df",2007:"9be5144d",2130:"6aca0963",2217:"7fbe9dae",2237:"02886161",2278:"0bc9e136",2279:"14e9bbca",2291:"742b3f1e",2334:"0d54c30f",2431:"bfceefed",2492:"04a5c13d",2618:"d8754146",2821:"25ddbeb7",3197:"a75f4a7b",3356:"916f8094",3624:"3861afa5",3815:"62684417",3993:"9cf23f81",4312:"3fc60254",4440:"3f63380d",4616:"c38d0c4b",4732:"37c818cd",4802:"c2bb0f45",4981:"a26c6f29",5011:"4a23b8e6",5149:"dc032f86",5480:"0ac51d82",5563:"ab94867b",5734:"a0ed7a40",5742:"dfe711f3",5953:"b825c418",5955:"5524a460",5986:"66f4e9f8",5996:"973aad9b",6087:"be66bc37",6241:"ddb16fb6",6438:"7425f799",6495:"bf671ca4",6567:"ba0bae2c",6590:"a0c8f815",6803:"9c357e99",6992:"6dfc4d99",7098:"1619d051",7194:"58f7d6fe",7520:"b544ad88",7592:"a68b31ac",7715:"833b5436",7873:"c42f29b1",7928:"08dd813c",8142:"f23a31c7",8249:"c71477f6",8401:"0e3115cb",8731:"da0fb09f",8756:"2c2f045b",8779:"94292821",8795:"1c60dc80",8897:"1b4b1274",9032:"6c8ca03c",9048:"df97c324",9178:"350e6ac9",9412:"2ce5e84d",9492:"ec5f11ac",9510:"2071eba3",9620:"117206fb",9647:"ac259cea",9659:"81999196",9717:"bdeb521f",9957:"366ae717"}[e]+".js",b.miniCssF=e=>{},b.g=function(){if("object"==typeof globalThis)return globalThis;try{return this||new Function("return this")()}catch(e){if("object"==typeof window)return window}}(),b.o=(e,a)=>Object.prototype.hasOwnProperty.call(e,a),r={},d="happyview-docs:",b.l=(e,a,t,c)=>{if(r[e])r[e].push(a);else{var f,o;if(void 0!==t)for(var n=document.getElementsByTagName("script"),i=0;i{f.onerror=f.onload=null,clearTimeout(s);var d=r[e];if(delete r[e],f.parentNode&&f.parentNode.removeChild(f),d&&d.forEach(e=>e(t)),a)return a(t)},s=setTimeout(l.bind(null,void 0,{type:"timeout",target:f}),12e4);f.onerror=l.bind(null,f.onerror),f.onload=l.bind(null,f.onload),o&&document.head.appendChild(f)}},b.r=e=>{"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},b.p="/",b.gca=function(e){return e={17896441:"8401","919abb94":"20","9985d6d5":"203","280f48ec":"787",fbd7a87c:"1361","22dd74f7":"1567",c985d368:"2007","23374ca6":"2278",bccc5e9b:"2431",b27c7406:"2618","5d3a5990":"3197",d2e94323:"3993","5fd2dbdb":"4440","121e86a6":"5011","539fb2af":"5563",aba21aa0:"5742","920ba7ef":"5953",fac68122:"5986",a77ba7fa:"6087",bde15bec:"6438","6acefe75":"6495","43880f7a":"6590","33ec3d65":"6803",a7bd4aaa:"7098",a4086a70:"7194","89877b86":"7520","009f1e98":"7715","111eee19":"8779",a51dfc20:"8897",a94703ab:"9048",e8644baa:"9178","846656e5":"9492","5e95c892":"9647",ec125a10:"9659",f142c65c:"9957"}[e]||e,b.p+b.u(e)},(()=>{var e={5354:0,1869:0};b.f.j=(a,t)=>{var r=b.o(e,a)?e[a]:void 0;if(0!==r)if(r)t.push(r[2]);else if(/^(1869|5354)$/.test(a))e[a]=0;else{var d=new Promise((t,d)=>r=e[a]=[t,d]);t.push(r[2]=d);var c=b.p+b.u(a),f=new Error;b.l(c,t=>{if(b.o(e,a)&&(0!==(r=e[a])&&(e[a]=void 0),r)){var d=t&&("load"===t.type?"missing":t.type),c=t&&t.target&&t.target.src;f.message="Loading chunk "+a+" failed.\n("+d+": "+c+")",f.name="ChunkLoadError",f.type=d,f.request=c,r[1](f)}},"chunk-"+a,a)}},b.O.j=a=>0===e[a];var a=(a,t)=>{var r,d,[c,f,o]=t,n=0;if(c.some(a=>0!==e[a])){for(r in f)b.o(f,r)&&(b.m[r]=f[r]);if(o)var i=o(b)}for(a&&a(t);n Authentication | HappyView - + diff --git a/getting-started/configuration/index.html b/getting-started/configuration/index.html --- a/getting-started/configuration/index.html +++ b/getting-started/configuration/index.html @@ -4,7 +4,7 @@ Configuration | HappyView - + diff --git a/getting-started/dashboard/index.html b/getting-started/dashboard/index.html --- a/getting-started/dashboard/index.html +++ b/getting-started/dashboard/index.html @@ -4,7 +4,7 @@ Dashboard | HappyView - + diff --git a/getting-started/quickstart/index.html b/getting-started/quickstart/index.html --- a/getting-started/quickstart/index.html +++ b/getting-started/quickstart/index.html @@ -4,7 +4,7 @@ Quickstart | HappyView - + diff --git a/guides/backfill/index.html b/guides/backfill/index.html --- a/guides/backfill/index.html +++ b/guides/backfill/index.html @@ -4,7 +4,7 @@ Backfill | HappyView - + diff --git a/guides/lexicons/index.html b/guides/lexicons/index.html --- a/guides/lexicons/index.html +++ b/guides/lexicons/index.html @@ -4,7 +4,7 @@ Lexicons | HappyView - + diff --git a/guides/scripting/index.html b/guides/scripting/index.html --- a/guides/scripting/index.html +++ b/guides/scripting/index.html @@ -4,7 +4,7 @@ Lua Scripting | HappyView - + @@ -38,7 +38,11 @@

Queries are unauthenticated: there is no caller_did or input.

Utility globals

Available in both queries and procedures:

-
FunctionReturnsDescription
now()stringCurrent UTC timestamp in ISO 8601 format
log(message)Log a message (appears in server logs at debug level)
TID()stringGenerate a fresh AT Protocol TID (13-character sortable identifier)
+
FunctionReturnsDescription
now()stringCurrent UTC timestamp in ISO 8601 format
log(message)Log a message (appears in server logs at debug level)
TID()stringGenerate a fresh AT Protocol TID (13-character sortable identifier)
toarray(table)tableMark a table as a JSON array for serialization (see below)
+

toarray

+

Lua tables don't distinguish between arrays and objects. When a table is serialized to JSON, an empty table {} becomes a JSON object {} instead of an array []. The toarray() function marks a table so it always serializes as a JSON array — even when empty.

+
return { items = toarray(results) }
-- With results: [{"name": "a"}, {"name": "b"}]
-- Without results: {"items": []} (not {"items": {}})
+

You don't need toarray() on results from db.query, db.search, db.backlinks, or db.raw — those already return properly marked arrays. Use it when you build a table yourself with table.insert().

Record API

The Record API is only available in procedure scripts. It handles creating, updating, loading, and deleting AT Protocol records. Writes are proxied to the caller's PDS and indexed locally.

Constructor

@@ -73,8 +77,20 @@
local result = db.query({
collection = "xyz.statusphere.status", -- required
did = "did:plc:abc", -- optional: filter by DID
limit = 20, -- optional: max 100, default 20
offset = 0, -- optional: for pagination
})

-- result.records — array of record tables (each includes a "uri" field)
-- result.cursor — present when more records exist

db.get

local record = db.get("at://did:plc:abc/xyz.statusphere.status/abc123")
-- Returns the record table or nil
-- The returned table includes a "uri" field
+

db.search

+
local result = db.search({
collection = "xyz.statusphere.status", -- required
field = "displayName", -- required: record field to search
query = "alice", -- required: search term
limit = 10, -- optional: max 100, default 10
})

-- result.records — array of matching records, ranked by relevance:
-- exact match > prefix match > contains match, then alphabetical
+ +

Find records that reference a given AT URI anywhere in their data. Useful for finding likes on a post, replies to a thread, or any record that links to another.

+
local result = db.backlinks({
collection = "xyz.statusphere.status", -- required
uri = "at://did:plc:abc/xyz.statusphere.status/foo", -- required: the URI to find references to
did = "did:plc:abc", -- optional: filter by DID
limit = 20, -- optional: max 100, default 20
offset = 0, -- optional: for pagination
})

-- result.records — array of records whose data contains the given URI
-- result.cursor — present when more records exist
+

The search checks the full record data, so it works regardless of which field holds the reference (subject, parent, reply.root, etc.).

db.count

local n = db.count("xyz.statusphere.status")
local n = db.count("xyz.statusphere.status", "did:plc:abc") -- filter by DID
+

db.raw

+

Run a raw SQL query against the database. Only SELECT statements are allowed.

+
local rows = db.raw(
"SELECT uri, did, record FROM records WHERE collection = $1 AND did = $2 LIMIT $3",
{ "xyz.statusphere.status", "did:plc:abc", 10 }
)

for _, row in ipairs(rows) do
-- row.uri, row.did, row.record (JSONB is returned as a Lua table)
end
+

Parameters are passed as an array and bound to $1, $2, etc. Supported parameter types: strings, integers, numbers, booleans, and nil.

+

Column types are mapped automatically:

+
Postgres typeLua type
TEXT, VARCHARstring
INT4, INT8integer
FLOAT4, FLOAT8number
BOOLboolean
JSON, JSONBtable
TIMESTAMPTZstring (ISO 8601)
Otherstring (fallback)

Standard libraries

The following Lua 5.4 standard library modules are available:

string

    @@ -173,6 +189,6 @@
  • Lexicons: Understand how record, query, and procedure lexicons work together
  • XRPC API: See how endpoints behave with and without Lua scripts
  • Dashboard: Use the web editor with context-aware completions
  • -
+
\ No newline at end of file diff --git a/reference/admin-api/index.html b/reference/admin-api/index.html --- a/reference/admin-api/index.html +++ b/reference/admin-api/index.html @@ -4,7 +4,7 @@ Admin API | HappyView - + diff --git a/reference/architecture/index.html b/reference/architecture/index.html --- a/reference/architecture/index.html +++ b/reference/architecture/index.html @@ -4,7 +4,7 @@ Architecture | HappyView - + diff --git a/reference/glossary/index.html b/reference/glossary/index.html --- a/reference/glossary/index.html +++ b/reference/glossary/index.html @@ -4,7 +4,7 @@ Glossary | HappyView - + diff --git a/reference/production-deployment/index.html b/reference/production-deployment/index.html --- a/reference/production-deployment/index.html +++ b/reference/production-deployment/index.html @@ -4,7 +4,7 @@ Deployment | HappyView - + diff --git a/reference/troubleshooting/index.html b/reference/troubleshooting/index.html --- a/reference/troubleshooting/index.html +++ b/reference/troubleshooting/index.html @@ -4,7 +4,7 @@ Troubleshooting | HappyView - + diff --git a/reference/xrpc-api/index.html b/reference/xrpc-api/index.html --- a/reference/xrpc-api/index.html +++ b/reference/xrpc-api/index.html @@ -4,7 +4,7 @@ XRPC API | HappyView - + diff --git a/tutorials/statusphere/index.html b/tutorials/statusphere/index.html --- a/tutorials/statusphere/index.html +++ b/tutorials/statusphere/index.html @@ -4,7 +4,7 @@ Tutorial: Statusphere with HappyView | HappyView - + diff --git a/getting-started/deployment/docker/index.html b/getting-started/deployment/docker/index.html --- a/getting-started/deployment/docker/index.html +++ b/getting-started/deployment/docker/index.html @@ -4,7 +4,7 @@ Local Development with Docker | HappyView - + diff --git a/getting-started/deployment/other/index.html b/getting-started/deployment/other/index.html --- a/getting-started/deployment/other/index.html +++ b/getting-started/deployment/other/index.html @@ -4,7 +4,7 @@ Local Development from Source | HappyView - + diff --git a/getting-started/deployment/railway/index.html b/getting-started/deployment/railway/index.html --- a/getting-started/deployment/railway/index.html +++ b/getting-started/deployment/railway/index.html @@ -4,7 +4,7 @@ Deploy on Railway | HappyView - + diff --git a/reference/scripts/batch-save/index.html b/reference/scripts/batch-save/index.html --- a/reference/scripts/batch-save/index.html +++ b/reference/scripts/batch-save/index.html @@ -4,7 +4,7 @@ Procedure: Batch Save | HappyView - + diff --git a/reference/scripts/cascading-delete/index.html b/reference/scripts/cascading-delete/index.html --- a/reference/scripts/cascading-delete/index.html +++ b/reference/scripts/cascading-delete/index.html @@ -4,7 +4,7 @@ Procedure: Cascading Delete | HappyView - + diff --git a/reference/scripts/complex-mutations/index.html b/reference/scripts/complex-mutations/index.html --- a/reference/scripts/complex-mutations/index.html +++ b/reference/scripts/complex-mutations/index.html @@ -4,7 +4,7 @@ Procedure: Complex Mutations | HappyView - + diff --git a/reference/scripts/create-record/index.html b/reference/scripts/create-record/index.html --- a/reference/scripts/create-record/index.html +++ b/reference/scripts/create-record/index.html @@ -4,7 +4,7 @@ Procedure: Create a Record | HappyView - + diff --git a/reference/scripts/expanded-query/index.html b/reference/scripts/expanded-query/index.html --- a/reference/scripts/expanded-query/index.html +++ b/reference/scripts/expanded-query/index.html @@ -4,7 +4,7 @@ Query: Expanded Query with Profiles | HappyView - + diff --git a/reference/scripts/get-record/index.html b/reference/scripts/get-record/index.html --- a/reference/scripts/get-record/index.html +++ b/reference/scripts/get-record/index.html @@ -4,7 +4,7 @@ Query: Get a Single Record | HappyView - + diff --git a/reference/scripts/list-or-fetch/index.html b/reference/scripts/list-or-fetch/index.html --- a/reference/scripts/list-or-fetch/index.html +++ b/reference/scripts/list-or-fetch/index.html @@ -4,7 +4,7 @@ Query: List or Fetch Records | HappyView - + diff --git a/reference/scripts/paginated-list/index.html b/reference/scripts/paginated-list/index.html --- a/reference/scripts/paginated-list/index.html +++ b/reference/scripts/paginated-list/index.html @@ -4,7 +4,7 @@ Query: Paginated List | HappyView - + diff --git a/reference/scripts/sidecar-records/index.html b/reference/scripts/sidecar-records/index.html --- a/reference/scripts/sidecar-records/index.html +++ b/reference/scripts/sidecar-records/index.html @@ -4,7 +4,7 @@ Procedure: Create Sidecar Records | HappyView - + diff --git a/reference/scripts/update-or-delete/index.html b/reference/scripts/update-or-delete/index.html --- a/reference/scripts/update-or-delete/index.html +++ b/reference/scripts/update-or-delete/index.html @@ -4,7 +4,7 @@ Procedure: Update or Delete | HappyView - + diff --git a/reference/scripts/upsert-record/index.html b/reference/scripts/upsert-record/index.html --- a/reference/scripts/upsert-record/index.html +++ b/reference/scripts/upsert-record/index.html @@ -4,7 +4,7 @@ Procedure: Upsert a Record | HappyView - +