From aa7f87ad362a2c64d9e6859ee6eae3831959a02a Mon Sep 17 00:00:00 2001 From: trezy Date: Fri, 27 Feb 2026 07:10:39 +0000 Subject: [PATCH] deploy: 49b743e28bcfeb9153baa5e11ffbb392c397da00 --- 404.html | 2 +- assets/js/920ba7ef.9eb561b1.js | 1 + assets/js/920ba7ef.b825c418.js | 1 - ...n.92290465.js => runtime~main.39d82192.js} | 2 +- getting-started/authentication/index.html | 2 +- getting-started/configuration/index.html | 2 +- getting-started/dashboard/index.html | 2 +- getting-started/deployment/docker/index.html | 2 +- getting-started/deployment/other/index.html | 2 +- getting-started/deployment/railway/index.html | 2 +- getting-started/quickstart/index.html | 2 +- guides/backfill/index.html | 2 +- guides/lexicons/index.html | 2 +- guides/scripting/index.html | 22 ++++++++++++++++--- index.html | 2 +- reference/admin-api/index.html | 2 +- reference/architecture/index.html | 2 +- reference/glossary/index.html | 2 +- reference/production-deployment/index.html | 2 +- reference/scripts/batch-save/index.html | 2 +- reference/scripts/cascading-delete/index.html | 2 +- .../scripts/complex-mutations/index.html | 2 +- reference/scripts/create-record/index.html | 2 +- reference/scripts/expanded-query/index.html | 2 +- reference/scripts/get-record/index.html | 2 +- reference/scripts/list-or-fetch/index.html | 2 +- reference/scripts/paginated-list/index.html | 2 +- reference/scripts/sidecar-records/index.html | 2 +- reference/scripts/update-or-delete/index.html | 2 +- reference/scripts/upsert-record/index.html | 2 +- reference/troubleshooting/index.html | 2 +- reference/xrpc-api/index.html | 2 +- tutorials/statusphere/index.html | 2 +- 33 files changed, 50 insertions(+), 34 deletions(-) create mode 100644 assets/js/920ba7ef.9eb561b1.js delete mode 100644 assets/js/920ba7ef.b825c418.js rename assets/js/{runtime~main.92290465.js => runtime~main.39d82192.js} (98%) diff --git a/404.html b/404.html index 0088208..0816093 100644 --- a/404.html +++ b/404.html @@ -4,7 +4,7 @@ HappyView - + diff --git a/assets/js/920ba7ef.9eb561b1.js b/assets/js/920ba7ef.9eb561b1.js new file mode 100644 index 0000000..c0bd730 --- /dev/null +++ b/assets/js/920ba7ef.9eb561b1.js @@ -0,0 +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 index 874174d..0000000 --- a/assets/js/920ba7ef.b825c418.js +++ /dev/null @@ -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.92290465.js b/assets/js/runtime~main.39d82192.js similarity index 98% rename from assets/js/runtime~main.92290465.js rename to assets/js/runtime~main.39d82192.js index f6ced1d..64c46c0 100644 --- a/assets/js/runtime~main.92290465.js +++ b/assets/js/runtime~main.39d82192.js @@ -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:"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{"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 Authentication | HappyView - + diff --git a/getting-started/configuration/index.html b/getting-started/configuration/index.html index 891ed60..b4896d2 100644 --- 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 index 3a90e86..9a492c3 100644 --- a/getting-started/dashboard/index.html +++ b/getting-started/dashboard/index.html @@ -4,7 +4,7 @@ Dashboard | HappyView - + diff --git a/getting-started/deployment/docker/index.html b/getting-started/deployment/docker/index.html index d41dd7d..cc768c3 100644 --- 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 index 0245cba..632323a 100644 --- 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 index 6ac762f..49769da 100644 --- 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/getting-started/quickstart/index.html b/getting-started/quickstart/index.html index 135db87..b27e8fb 100644 --- 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 index 66b3b67..f008175 100644 --- 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 index 25f9906..3cfcdf9 100644 --- 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 index 67435c7..3ec495f 100644 --- 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/index.html b/index.html index efeff8e..ff389c1 100644 --- a/index.html +++ b/index.html @@ -4,7 +4,7 @@ HappyView - + diff --git a/reference/admin-api/index.html b/reference/admin-api/index.html index 0a5e54d..9375fa3 100644 --- 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 index a214a42..2daa2cb 100644 --- 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 index 38b8502..b29d20b 100644 --- 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 index ea6b506..ccc649a 100644 --- a/reference/production-deployment/index.html +++ b/reference/production-deployment/index.html @@ -4,7 +4,7 @@ Deployment | HappyView - + diff --git a/reference/scripts/batch-save/index.html b/reference/scripts/batch-save/index.html index 51b699f..e2cc27d 100644 --- 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 index 9882d4a..f128a2f 100644 --- 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 index 741d5ff..05df918 100644 --- 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 index d74186f..7f52383 100644 --- 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 index 8563b9d..73921b1 100644 --- 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 index e2d8e06..765a88b 100644 --- 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 index d9c24a5..f62b9b4 100644 --- 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 index 92960d4..d5845da 100644 --- 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 index 7ae309e..fe94ea7 100644 --- 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 index a4c188c..5b5229a 100644 --- 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 index f06d97d..b98ad82 100644 --- a/reference/scripts/upsert-record/index.html +++ b/reference/scripts/upsert-record/index.html @@ -4,7 +4,7 @@ Procedure: Upsert a Record | HappyView - + diff --git a/reference/troubleshooting/index.html b/reference/troubleshooting/index.html index 4a45743..5650f88 100644 --- 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 index b0e3fc2..17cbde2 100644 --- 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 index 6893c6a..979ae6e 100644 --- a/tutorials/statusphere/index.html +++ b/tutorials/statusphere/index.html @@ -4,7 +4,7 @@ Tutorial: Statusphere with HappyView | HappyView - + -- 2.51.2