Added some better error handling for the electric sync
🚀 Publish Trigger.dev Docker / units (push) Failing after 11m46s
🚀 Publish Trigger.dev Docker / typecheck (push) Failing after 11m47s
🚀 Publish Trigger.dev Docker / publish (push) Has been skipped
🚀 Publish Trigger.dev Docker / publish-infra (push) Has been skipped

This commit is contained in:
Matt Aitken
2024-09-04 14:23:11 +01:00
parent 6bdbc6808b
commit cd94cc9736
3 changed files with 144 additions and 88 deletions
+53 -38
View File
@@ -6,45 +6,60 @@ import { getUserId } from "~/services/session.server";
import { longPollingFetch } from "~/utils/longPollingFetch";
export async function loader({ params, request }: LoaderFunctionArgs) {
const userId = await getUserId(request);
try {
const userId = await getUserId(request);
logger.log(`/sync/traces/${params.traceId}`, { userId });
logger.log(`/sync/traces/${params.traceId}`, { userId });
if (!userId) {
return new Response("No user found in cookie", { status: 401 });
if (!userId) {
return new Response("No user found in cookie", { status: 401 });
}
const trace = await $replica.taskEvent.findFirst({
select: {
organizationId: true,
},
where: {
traceId: params.traceId,
},
});
if (!trace) {
return new Response("No trace found", { status: 404 });
}
const member = await $replica.orgMember.findFirst({
where: {
organizationId: trace.organizationId,
userId,
},
});
if (!member) {
return new Response("Not a member of this org", { status: 401 });
}
const url = new URL(request.url);
const originUrl = new URL(`${env.ELECTRIC_ORIGIN}/v1/shape/public."TaskEvent"`);
url.searchParams.forEach((value, key) => {
originUrl.searchParams.set(key, value);
});
originUrl.searchParams.set("where", `"traceId"='${params.traceId}'`);
return longPollingFetch(originUrl.toString());
} catch (error) {
if (error instanceof Response) {
// Error responses from longPollingFetch
return error;
} else if (error instanceof TypeError) {
// Unexpected errors
logger.error("Unexpected error in loader:", { error: error.message });
return new Response("An unexpected error occurred", { status: 500 });
} else {
// Unknown errors
logger.error("Unknown error occurred in loader, not Error", { error: JSON.stringify(error) });
return new Response("An unknown error occurred", { status: 500 });
}
}
const trace = await $replica.taskEvent.findFirst({
select: {
organizationId: true,
},
where: {
traceId: params.traceId,
},
});
if (!trace) {
return new Response("No trace found", { status: 404 });
}
const member = await $replica.orgMember.findFirst({
where: {
organizationId: trace.organizationId,
userId,
},
});
if (!member) {
return new Response("Not a member of this org", { status: 401 });
}
const url = new URL(request.url);
const originUrl = new URL(`${env.ELECTRIC_ORIGIN}/v1/shape/public."TaskEvent"`);
url.searchParams.forEach((value, key) => {
originUrl.searchParams.set(key, value);
});
originUrl.searchParams.set("where", `"traceId"='${params.traceId}'`);
return longPollingFetch(originUrl.toString());
}
@@ -11,50 +11,65 @@ const Params = z.object({
});
export async function loader({ params, request }: LoaderFunctionArgs) {
const userId = await getUserId(request);
const { traceId } = Params.parse(params);
try {
const userId = await getUserId(request);
const { traceId } = Params.parse(params);
logger.log(`/sync/runs/${traceId}`, { userId });
logger.log(`/sync/runs/${traceId}`, { userId });
if (!userId) {
return new Response("No user found in cookie", { status: 401 });
}
if (!userId) {
return new Response("No user found in cookie", { status: 401 });
}
const run = await $replica.taskRun.findFirst({
select: {
project: {
select: {
organizationId: true,
const run = await $replica.taskRun.findFirst({
select: {
project: {
select: {
organizationId: true,
},
},
},
},
where: {
traceId,
},
});
where: {
traceId,
},
});
if (!run) {
return new Response("No run found", { status: 404 });
if (!run) {
return new Response("No run found", { status: 404 });
}
const member = await $replica.orgMember.findFirst({
where: {
organizationId: run.project.organizationId,
userId,
},
});
if (!member) {
return new Response("Not a member of this org", { status: 401 });
}
const url = new URL(request.url);
const originUrl = new URL(`${env.ELECTRIC_ORIGIN}/v1/shape/public."TaskRun"`);
url.searchParams.forEach((value, key) => {
originUrl.searchParams.set(key, value);
});
originUrl.searchParams.set("where", `"traceId"='${traceId}'`);
return longPollingFetch(originUrl.toString());
} catch (error) {
if (error instanceof Response) {
// Error responses from longPollingFetch
return error;
} else if (error instanceof TypeError) {
// Unexpected errors
logger.error("Unexpected error in loader:", { error: error.message });
return new Response("An unexpected error occurred", { status: 500 });
} else {
// Unknown errors
logger.error("Unknown error occurred in loader, not Error", { error: JSON.stringify(error) });
return new Response("An unknown error occurred", { status: 500 });
}
}
const member = await $replica.orgMember.findFirst({
where: {
organizationId: run.project.organizationId,
userId,
},
});
if (!member) {
return new Response("Not a member of this org", { status: 401 });
}
const url = new URL(request.url);
const originUrl = new URL(`${env.ELECTRIC_ORIGIN}/v1/shape/public."TaskRun"`);
url.searchParams.forEach((value, key) => {
originUrl.searchParams.set(key, value);
});
originUrl.searchParams.set("where", `"traceId"='${traceId}'`);
return longPollingFetch(originUrl.toString());
}
+37 -11
View File
@@ -2,18 +2,44 @@
// erroneously (saying the body is gzipped when it's not) so we'll just remove
// them to avoid content decoding errors in the browser.
//
import { logger } from "~/services/logger.server";
// Similar-ish problem to https://github.com/wintercg/fetch/issues/23
export async function longPollingFetch(url: string, options?: RequestInit) {
let response = await fetch(url, options);
if (response.headers.get(`content-encoding`)) {
const headers = new Headers(response.headers);
headers.delete(`content-encoding`);
headers.delete(`content-length`);
response = new Response(response.body, {
status: response.status,
statusText: response.statusText,
headers,
});
try {
let response = await fetch(url, options);
// Check if the response is ok (status in the range 200-299)
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
if (response.headers.get(`content-encoding`)) {
const headers = new Headers(response.headers);
headers.delete(`content-encoding`);
headers.delete(`content-length`);
response = new Response(response.body, {
status: response.status,
statusText: response.statusText,
headers,
});
}
return response;
} catch (error) {
if (error instanceof TypeError) {
// Network error or other fetch-related errors
logger.error("Network error:", { error: error.message });
throw new Response("Network error occurred", { status: 503 });
} else if (error instanceof Error) {
// HTTP errors or other known errors
logger.error("Fetch error:", { error: error.message });
throw new Response(error.message, { status: 500 });
} else {
// Unknown errors
logger.error("Unknown error occurred during fetch");
throw new Response("An unknown error occurred", { status: 500 });
}
}
return response;
}