- Guides
- Recipes
Algolia sync
Push records to an Algolia search index whenever they are created, updated, or deleted on the network.
Trigger: record.index:<nsid>. Needs: happyview-http. Script variables: ALGOLIA_APP_ID, ALGOLIA_API_KEY.
local json = require("internal.json")local http = require("happyview.http")
-- Percent-encode everything but unreserved characters, so the AT URI fits in a path segmentlocal function enc(s) return (s:gsub("[^%w%-_%.~]", function(c) return string.format("%%%02X", c:byte()) end))end
function handle(input, ctx) local url = "https://" .. ctx.env.ALGOLIA_APP_ID .. ".algolia.net/1/indexes/records/" .. enc(input.uri) local headers = { ["X-Algolia-API-Key"] = ctx.env.ALGOLIA_API_KEY, ["X-Algolia-Application-Id"] = ctx.env.ALGOLIA_APP_ID, ["Content-Type"] = "application/json", }
local res if input.action == "delete" then res = http.delete(url, { headers = headers }) else res = http.put(url, { headers = headers, body = json.encode({ objectID = input.uri, collection = input.collection, did = input.did, record = input.record, }), }) end
if res.status >= 400 then error("algolia answered " .. res.status) end
-- On delete `record` is null, which still lets the delete proceed. return input.record or trueendimport http from "happyview.http";
export default async function handle(input, ctx) { const url = `https://${ctx.env.ALGOLIA_APP_ID}.algolia.net/1/indexes/records/${encodeURIComponent(input.uri)}`; const headers = { "X-Algolia-API-Key": ctx.env.ALGOLIA_API_KEY, "X-Algolia-Application-Id": ctx.env.ALGOLIA_APP_ID, "Content-Type": "application/json", };
const res = input.action === "delete" ? await http.delete(url, { headers }) : await http.put(url, { headers, body: JSON.stringify({ objectID: input.uri, collection: input.collection, did: input.did, record: input.record, }), });
if (res.status >= 400) { throw new Error(`algolia answered ${res.status}`); }
// `record` is null on delete, so this returns `true` and the delete proceeds. return input.record ?? true;}import http from "happyview.http";
interface RecordEvent { action: "create" | "update" | "delete"; uri: string; did: string; collection: string; record: Record<string, unknown> | null;}
interface Context { env: Record<string, string>;}
export default async function handle(input: RecordEvent, ctx: Context) { const url = `https://${ctx.env.ALGOLIA_APP_ID}.algolia.net/1/indexes/records/${encodeURIComponent(input.uri)}`; const headers = { "X-Algolia-API-Key": ctx.env.ALGOLIA_API_KEY, "X-Algolia-Application-Id": ctx.env.ALGOLIA_APP_ID, "Content-Type": "application/json", };
const res = input.action === "delete" ? await http.delete(url, { headers }) : await http.put(url, { headers, body: JSON.stringify({ objectID: input.uri, collection: input.collection, did: input.did, record: input.record, }), });
if (res.status >= 400) { throw new Error(`algolia answered ${res.status}`); }
// `record` is null on delete, so this returns `true` and the delete proceeds. return input.record ?? true;}How it works
Section titled “How it works”- On create or update: sends a
PUTrequest to Algolia’s index API with the record data, using the AT URI as theobjectID. Algolia replaces an object that already exists. - On delete: sends a
DELETErequest to remove the object from the index by its AT URI.
The AT URI is percent-encoded in the request path, with enc in Lua and encodeURIComponent in JavaScript and TypeScript, because its / characters would otherwise be read as path separators.
input is the record event. happyview.http returns a 4xx or 5xx answer as an ordinary result, so the script checks status and raises for it. A failed record script is retried and then dead-lettered.
Configuration
Section titled “Configuration”Set these as script variables from the dashboard or the admin API:
| Variable | Value |
|---|---|
ALGOLIA_ | Your Algolia Application ID |
ALGOLIA_ | An Algolia API key with the addObject and deleteObject ACLs |
The index name is the records segment of the URL. Change it to any name you like.
Use case
Section titled “Use case”This record script keeps an external search index in step with your indexed records as events arrive, with no polling or scheduled jobs.
Combine this with a query script that searches Algolia instead of the local database for full-text search beyond the substring match of happyview.db’s search.
© HappyProto

