A lexicon-driven AppView for ATProto. happyview.dev
backfill firehose jetstream atproto appview oauth lexicon
8
fork

Configure Feed

Select the types of activity you want to include in your feed.

feat: add missing HappyView Lua APIs to autocomplete and hover docs

Trezy cb4b1c1a 3d667250

+115 -8
+7 -4
web/src/components/monaco-editor.tsx
··· 317 317 position.column - 1, 318 318 ); 319 319 320 - // Inside db.query({ ... }) — suggest option keys 321 - if (/db\.query\(\s*\{[^}]*$/.test(textBeforeCursor)) { 320 + // Inside db.query/search/backlinks({ ... }) — suggest option keys 321 + const dbOptionsMatch = textBeforeCursor.match( 322 + /db\.(query|search|backlinks)\(\s*\{[^}]*$/, 323 + ); 324 + if (dbOptionsMatch) { 322 325 const optionEntries = 323 - completionsRef.current?.["db.query"] ?? []; 326 + completionsRef.current?.[`db.${dbOptionsMatch[1]}`] ?? []; 324 327 if (optionEntries.length) { 325 328 // Check for collection = " inside db.query — offer NSID completions 326 329 const collectionQuoteMatch = textBeforeCursor.match( ··· 366 369 } 367 370 } 368 371 369 - // Collection NSID completions for collection = " (outside db.query) and db.count 372 + // Collection NSID completions for collection = " (outside db options) and db.count 370 373 const collectionAssignMatch = textBeforeCursor.match( 371 374 /(?:db\.count\(\s*"([^"]*)$|collection\s*=\s*"([^"]*)$)/, 372 375 );
+73 -2
web/src/lib/lua-completions.ts
··· 26 26 "string", "table", "math", "coroutine", "utf8", 27 27 // HappyView sandbox globals 28 28 "input", "params", "caller_did", "collection", "method", 29 - "now", "log", "TID", 29 + "now", "log", "TID", "toarray", "env", 30 + // Hook-specific globals 31 + "action", "uri", "did", "rkey", "record", 32 + // HappyView API modules 33 + "http", "xrpc", "atproto", 30 34 ]; 31 35 32 36 export const LUA_SNIPPETS: LuaSnippetEntry[] = [ ··· 111 115 { label: "delete", detail: "method", description: "Delete this record from PDS and database — r:delete()" }, 112 116 { label: "set_key_type", detail: "method", description: "Set the record key type (tid, any, nsid, literal:*) — r:set_key_type(type)" }, 113 117 { label: "set_rkey", detail: "method", description: "Set a specific rkey for this record — r:set_rkey(key)" }, 118 + { label: "set_repo", detail: "method", description: "Override the repo DID (instead of caller_did) — r:set_repo(did)" }, 114 119 { label: "generate_rkey", detail: "method", description: "Generate an rkey based on _key_type — r:generate_rkey()" }, 115 120 { label: "_uri", detail: "string?", description: "AT URI of the record (set after save)" }, 116 121 { label: "_cid", detail: "string?", description: "CID of the record (set after save)" }, ··· 136 141 description: "Count records — db.count(collection, did?) → integer", 137 142 insertText: "count(${1:collection})", 138 143 }, 144 + { 145 + label: "search", 146 + detail: "function", 147 + description: "Search records by field value — db.search({ collection, field, query, limit? }) → { records }", 148 + insertText: "search({\n\tcollection = ${1:collection},\n\tfield = ${2:\"field\"},\n\tquery = ${3:\"search term\"},\n})", 149 + }, 150 + { 151 + label: "backlinks", 152 + detail: "function", 153 + description: "Find records referencing a URI — db.backlinks({ collection, uri, did?, limit?, cursor? }) → { records, cursor? }", 154 + insertText: "backlinks({\n\tcollection = ${1:collection},\n\turi = ${2:uri},\n})", 155 + }, 156 + { 157 + label: "raw", 158 + detail: "function", 159 + description: "Execute a raw SQL query — db.raw(sql, params?) → rows[]", 160 + insertText: "raw(${1:\"SELECT * FROM records WHERE collection = ?\"}${2:, \\{${3}\\}})", 161 + }, 162 + { 163 + label: "backend", 164 + detail: "function", 165 + description: "Returns the database backend — db.backend() → \"sqlite\" or \"postgres\"", 166 + insertText: "backend()", 167 + }, 139 168 ], 140 169 "db.query": [ 141 170 { label: "collection", detail: "string", description: "Collection NSID (required)" }, 142 171 { label: "did", detail: "string?", description: "Filter records by DID" }, 143 172 { label: "limit", detail: "integer?", description: "Max records to return (max 100, default 20)" }, 144 - { label: "offset", detail: "integer?", description: "Pagination offset (default 0)" }, 173 + { label: "offset", detail: "integer?", description: "Pagination offset (default 0, used with custom sort)" }, 174 + { label: "cursor", detail: "string?", description: "Pagination cursor from previous query" }, 175 + { label: "sort", detail: "string?", description: "Field name to sort by" }, 176 + { label: "sortDirection", detail: "string?", description: "Sort direction — \"asc\" or \"desc\" (default \"desc\")" }, 177 + ], 178 + "db.search": [ 179 + { label: "collection", detail: "string", description: "Collection NSID (required)" }, 180 + { label: "field", detail: "string", description: "JSON field to search (required)" }, 181 + { label: "query", detail: "string", description: "Search term (required)" }, 182 + { label: "limit", detail: "integer?", description: "Max records to return (max 100, default 10)" }, 183 + ], 184 + "db.backlinks": [ 185 + { label: "collection", detail: "string", description: "Collection NSID (required)" }, 186 + { label: "uri", detail: "string", description: "Target URI to find references to (required)" }, 187 + { label: "did", detail: "string?", description: "Filter by DID" }, 188 + { label: "limit", detail: "integer?", description: "Max records to return (max 100, default 20)" }, 189 + { label: "cursor", detail: "string?", description: "Pagination cursor" }, 145 190 ], 146 191 "db.query_result": [ 147 192 { label: "records", detail: "table[]", description: "Array of record tables (each includes uri)" }, ··· 218 263 { label: "codes", detail: "function", description: "Iterator over UTF-8 codepoints — utf8.codes(s [, lax])" }, 219 264 { label: "len", detail: "function", description: "UTF-8 string length — utf8.len(s [, i [, j [, lax]]])" }, 220 265 { label: "offset", detail: "function", description: "Byte offset of nth character — utf8.offset(s, n [, i])" }, 266 + ], 267 + // HappyView HTTP API 268 + http: [ 269 + { label: "get", detail: "function", description: "HTTP GET request — http.get(url, options?) → {status, body, headers}", insertText: "get(${1:url})" }, 270 + { label: "post", detail: "function", description: "HTTP POST request — http.post(url, options?) → {status, body, headers}", insertText: "post(${1:url}, {\n\theaders = { [\"content-type\"] = \"application/json\" },\n\tbody = ${2:body},\n})" }, 271 + { label: "put", detail: "function", description: "HTTP PUT request — http.put(url, options?) → {status, body, headers}", insertText: "put(${1:url}, {\n\tbody = ${2:body},\n})" }, 272 + { label: "patch", detail: "function", description: "HTTP PATCH request — http.patch(url, options?) → {status, body, headers}", insertText: "patch(${1:url}, {\n\tbody = ${2:body},\n})" }, 273 + { label: "delete", detail: "function", description: "HTTP DELETE request — http.delete(url, options?) → {status, body, headers}", insertText: "delete(${1:url})" }, 274 + { label: "head", detail: "function", description: "HTTP HEAD request — http.head(url, options?) → {status, headers}", insertText: "head(${1:url})" }, 275 + ], 276 + "http.options": [ 277 + { label: "headers", detail: "table?", description: "Request headers as key-value pairs" }, 278 + { label: "body", detail: "string?", description: "Request body (for POST, PUT, PATCH, DELETE)" }, 279 + ], 280 + // HappyView XRPC API 281 + xrpc: [ 282 + { label: "query", detail: "function", description: "XRPC query — xrpc.query(method, params?) → {status, body}", insertText: "query(${1:\"com.atproto.repo.describeRepo\"}, {\n\t${2}\n})" }, 283 + { label: "procedure", detail: "function", description: "XRPC procedure (requires caller_did) — xrpc.procedure(method, input, params?) → {status, body}", insertText: "procedure(${1:\"method\"}, {\n\t${2}\n})" }, 284 + ], 285 + // HappyView AT Protocol API 286 + atproto: [ 287 + { label: "resolve_service_endpoint", detail: "function", description: "Resolve a DID to its PDS endpoint URL — atproto.resolve_service_endpoint(did) → string or nil", insertText: "resolve_service_endpoint(${1:did})" }, 288 + { label: "get_labels", detail: "function", description: "Get labels for a URI — atproto.get_labels(uri) → label[]", insertText: "get_labels(${1:uri})" }, 289 + { label: "get_labels_batch", detail: "function", description: "Get labels for multiple URIs — atproto.get_labels_batch({uri1, uri2}) → {[uri]: label[]}", insertText: "get_labels_batch(${1:uris})" }, 290 + { label: "sign", detail: "function", description: "Sign a record (requires attestation signer) — atproto.sign(record) → signature", insertText: "sign(${1:record})" }, 291 + { label: "verify_signature", detail: "function", description: "Verify a record signature — atproto.verify_signature(record, sig, repo_did) → boolean", insertText: "verify_signature(${1:record}, ${2:sig}, ${3:repo_did})" }, 221 292 ], 222 293 }; 223 294
+35 -2
web/src/lib/lua-hover.ts
··· 52 52 // ── HappyView sandbox globals ───────────────────────────────────────── 53 53 ["input", { signature: "input", description: "Procedure input table (from request body)" }], 54 54 ["params", { signature: "params", description: "Query parameters table (from URL query string)" }], 55 - ["caller_did", { signature: "caller_did", description: "DID of the authenticated caller" }], 55 + ["caller_did", { signature: "caller_did", description: "DID of the authenticated caller (nil for unauthenticated queries)" }], 56 56 ["collection", { signature: "collection", description: "Target collection NSID for this lexicon" }], 57 57 ["method", { signature: "method", description: "XRPC method name being called" }], 58 + ["env", { signature: "env", description: "Script variables table (configured per-lexicon in the dashboard)" }], 58 59 ["now", { signature: "now()", description: "Current UTC timestamp as ISO 8601 string" }], 59 60 ["log", { signature: "log(···)", description: "Log values to server console" }], 60 61 ["TID", { signature: "TID()", description: "Generate a new TID (timestamp identifier)" }], 62 + ["toarray", { signature: "toarray(table)", description: "Mark a table as a JSON array for serialization (ensures empty tables serialize as [] not {})" }], 63 + 64 + // ── Hook-specific globals ──────────────────────────────────────────── 65 + ["action", { signature: "action", description: "Hook action type — \"create\", \"update\", or \"delete\"" }], 66 + ["uri", { signature: "uri", description: "AT URI of the record being processed" }], 67 + ["did", { signature: "did", description: "DID of the repo owner" }], 68 + ["rkey", { signature: "rkey", description: "Record key of the record being processed" }], 69 + ["record", { signature: "record", description: "Record data table (nil on delete actions)" }], 61 70 62 71 // ── string module ───────────────────────────────────────────────────── 63 72 ["string.byte", { signature: "string.byte(s [, i [, j]])", description: "Returns internal numeric codes of characters", module: "string" }], ··· 139 148 ["Record:delete", { signature: "record:delete()", description: "Delete this record from PDS and database" }], 140 149 ["Record:set_key_type", { signature: "record:set_key_type(type)", description: "Set the record key type (tid, any, nsid, literal:*)" }], 141 150 ["Record:set_rkey", { signature: "record:set_rkey(key)", description: "Set a specific rkey for this record" }], 151 + ["Record:set_repo", { signature: "record:set_repo(did)", description: "Override the repo DID (write to a different user's repo instead of caller_did)" }], 142 152 ["Record:generate_rkey", { signature: "record:generate_rkey()", description: "Generate an rkey based on the record's _key_type" }], 143 153 144 154 // ── HappyView db API ────────────────────────────────────────────────── 145 - ["db.query", { signature: "db.query({collection, did?, limit?, offset?})", description: "Query records — returns {records, cursor?}", module: "db" }], 155 + ["db.query", { signature: "db.query({collection, did?, limit?, offset?, cursor?, sort?, sortDirection?})", description: "Query records — returns {records, cursor?}", module: "db" }], 146 156 ["db.get", { signature: "db.get(uri)", description: "Get a single record by AT URI — returns record or nil", module: "db" }], 147 157 ["db.count", { signature: "db.count(collection [, did])", description: "Count records in a collection", module: "db" }], 158 + ["db.search", { signature: "db.search({collection, field, query, limit?})", description: "Search records by field value — returns {records}", module: "db" }], 159 + ["db.backlinks", { signature: "db.backlinks({collection, uri, did?, limit?, cursor?})", description: "Find records that reference a URI via record_refs — returns {records, cursor?}", module: "db" }], 160 + ["db.raw", { signature: "db.raw(sql [, params])", description: "Execute a raw SQL query — returns array of row tables", module: "db" }], 161 + ["db.backend", { signature: "db.backend()", description: "Returns the database backend — \"sqlite\" or \"postgres\"", module: "db" }], 162 + 163 + // ── HappyView HTTP API ─────────────────────────────────────────────── 164 + ["http.get", { signature: "http.get(url [, options])", description: "HTTP GET request — returns {status, body, headers}", module: "http" }], 165 + ["http.post", { signature: "http.post(url [, options])", description: "HTTP POST request — returns {status, body, headers}", module: "http" }], 166 + ["http.put", { signature: "http.put(url [, options])", description: "HTTP PUT request — returns {status, body, headers}", module: "http" }], 167 + ["http.patch", { signature: "http.patch(url [, options])", description: "HTTP PATCH request — returns {status, body, headers}", module: "http" }], 168 + ["http.delete", { signature: "http.delete(url [, options])", description: "HTTP DELETE request — returns {status, body, headers}", module: "http" }], 169 + ["http.head", { signature: "http.head(url [, options])", description: "HTTP HEAD request — returns {status, headers}", module: "http" }], 170 + 171 + // ── HappyView XRPC API ────────────────────────────────────────────── 172 + ["xrpc.query", { signature: "xrpc.query(method [, params])", description: "Make an XRPC query to a PDS — returns {status, body} where body is JSON string", module: "xrpc" }], 173 + ["xrpc.procedure", { signature: "xrpc.procedure(method, input [, params])", description: "Make an XRPC procedure call (requires caller_did) — returns {status, body}", module: "xrpc" }], 174 + 175 + // ── HappyView AT Protocol API ──────────────────────────────────────── 176 + ["atproto.resolve_service_endpoint", { signature: "atproto.resolve_service_endpoint(did)", description: "Resolve a DID to its PDS endpoint URL — returns string or nil", module: "atproto" }], 177 + ["atproto.get_labels", { signature: "atproto.get_labels(uri)", description: "Get labels for a URI — returns array of {src, uri, val, cts}", module: "atproto" }], 178 + ["atproto.get_labels_batch", { signature: "atproto.get_labels_batch({uri1, uri2, ...})", description: "Get labels for multiple URIs — returns table keyed by URI", module: "atproto" }], 179 + ["atproto.sign", { signature: "atproto.sign(record)", description: "Sign a record (requires attestation signer, procedure scripts only) — returns signature object", module: "atproto" }], 180 + ["atproto.verify_signature", { signature: "atproto.verify_signature(record, sig, repo_did)", description: "Verify a record signature — returns boolean", module: "atproto" }], 148 181 ]);