Skip to content

Instantly share code, notes, and snippets.

@marpe
Created October 12, 2025 11:48
Show Gist options
  • Save marpe/9d8e6223edd7dbcd4b0cf678b8c958d5 to your computer and use it in GitHub Desktop.
Save marpe/9d8e6223edd7dbcd4b0cf678b8c958d5 to your computer and use it in GitHub Desktop.
import "jsr:@supabase/functions-js/edge-runtime.d.ts";
import { ELECTRIC_PROTOCOL_QUERY_PARAMS } from "npm:@electric-sql/[email protected]";
import { createClient } from "npm:@supabase/supabase-js@2";
const electricApiUrl = Deno.env.get("ELECTRIC_API_URL")!;
const supabaseKey = Deno.env.get("SUPABASE_PUBLISHABLE_KEY") ??
Deno.env.get("SUPABASE_ANON_KEY") ??
"";
const supabaseUrl = Deno.env.get("SUPABASE_URL") ?? "";
const sourceId = Deno.env.get("ELECTRIC_SOURCE_ID");
const sourceSecret = Deno.env.get("ELECTRIC_SOURCE_SECRET");
console.log(
`Hello from the electric function! using api url: ${electricApiUrl}`,
);
const corsHeaders = {
"access-control-allow-origin": "*",
"access-control-allow-methods": "*",
"access-control-allow-headers": "*",
};
Deno.serve(async (req) => {
if (req.method === "OPTIONS") {
return new Response("ok", { headers: corsHeaders });
}
try {
const requestHeaders = new Headers(req.headers);
const authHeader = requestHeaders.get("Authorization");
requestHeaders.delete("Authorization");
const requestUrl = new URL(req.url);
if (!authHeader) {
throw new Error("Authorization header is missing");
}
const supabaseClient = createClient(
supabaseUrl,
supabaseKey,
{
global: {
headers: { Authorization: authHeader },
},
},
);
const token = authHeader.replace("Bearer ", "");
const { data: userData } = await supabaseClient.auth.getUser(token);
if (!userData?.user?.id) {
throw new Error(`User id is not set:\ntoken: ${token}\n\nuser: ${JSON.stringify(userData, null, 2)}`);
}
const syncUrl = new URL(`${electricApiUrl}/v1/shape`);
requestUrl.searchParams.forEach((value, key) => {
if (ELECTRIC_PROTOCOL_QUERY_PARAMS.includes(key)) {
syncUrl.searchParams.set(key, value);
}
});
if (sourceId) {
syncUrl.searchParams.set("source_id", sourceId);
}
if (sourceSecret) {
syncUrl.searchParams.set("secret", sourceSecret);
}
const table = requestUrl.searchParams.get("table");
if (!table) {
throw new Error("table is not set");
}
syncUrl.searchParams.set("table", table);
const whereColumn = table === "user" ? "id" : "user_id";
syncUrl.searchParams.set("where", `${whereColumn}='${userData.user.id}'`);
console.log(`fetching: ${syncUrl}`);
const fetchResponse = await fetch(syncUrl, {
signal: AbortSignal.timeout(25000),
headers: requestHeaders,
});
console.log(`fetch done, status: ${fetchResponse.status}`);
const responseHeaders = new Headers(fetchResponse.headers);
responseHeaders.delete(`content-encoding`);
responseHeaders.delete(`content-length`);
for (const [key, value] of Object.entries(corsHeaders)) {
responseHeaders.set(key, value);
}
return new Response(
fetchResponse.body,
{
statusText: fetchResponse.statusText,
headers: responseHeaders,
status: fetchResponse.status,
},
);
} catch (e) {
console.error(`Error: "${(e as any).name}"`, e);
const statusCode = (e as any)?.name === "TimeoutError" ? 408 : 500;
return new Response(
JSON.stringify({ error: (e as any)?.message }),
{
headers: { ...corsHeaders },
status: statusCode,
},
);
}
});
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment