From e06bbf35096fa43195a870ac13a212f55cce47a1 Mon Sep 17 00:00:00 2001 From: wa0x6e <495709+wa0x6e@users.noreply.github.com> Date: Sat, 14 Feb 2026 15:55:57 -0500 Subject: [PATCH 1/6] fix: centralize queryAsync error handling in mysql helper Fixes SNAPSHOT-HUB-35 Co-Authored-By: Claude Opus 4.6 --- src/graphql/operations/aliases.ts | 10 +--- src/graphql/operations/follows.ts | 12 +---- src/graphql/operations/leaderboards.ts | 10 +--- src/graphql/operations/messages.ts | 10 +--- src/graphql/operations/proposal.ts | 12 +---- src/graphql/operations/proposals.ts | 12 +---- src/graphql/operations/roles.ts | 46 +++++++--------- src/graphql/operations/statement.ts | 14 ++--- src/graphql/operations/statements.ts | 12 +---- src/graphql/operations/subscriptions.ts | 12 +---- src/graphql/operations/users.ts | 70 +++++++++++------------- src/graphql/operations/votes.ts | 72 +++++++++---------------- src/helpers/mysql.ts | 15 +++++- 13 files changed, 106 insertions(+), 201 deletions(-) diff --git a/src/graphql/operations/aliases.ts b/src/graphql/operations/aliases.ts index a6f75b28..3917cce5 100644 --- a/src/graphql/operations/aliases.ts +++ b/src/graphql/operations/aliases.ts @@ -1,5 +1,3 @@ -import { capture } from '@snapshot-labs/snapshot-sentry'; -import log from '../../helpers/log'; import db from '../../helpers/mysql'; import { buildWhereQuery, checkLimits } from '../helpers'; @@ -33,11 +31,5 @@ export default async function (parent, args) { `; params.push(skip, first); - try { - return await db.queryAsync(query, params); - } catch (e: any) { - log.error(`[graphql] aliases, ${JSON.stringify(e)}`); - capture(e, { args }); - return Promise.reject(new Error('request failed')); - } + return await db.queryAsync(query, params); } diff --git a/src/graphql/operations/follows.ts b/src/graphql/operations/follows.ts index 262bcaa0..fa1cbc2b 100644 --- a/src/graphql/operations/follows.ts +++ b/src/graphql/operations/follows.ts @@ -1,5 +1,3 @@ -import { capture } from '@snapshot-labs/snapshot-sentry'; -import log from '../../helpers/log'; import db from '../../helpers/mysql'; import { buildWhereQuery, checkLimits, formatFollow } from '../helpers'; @@ -46,12 +44,6 @@ export default async function (parent, args) { ORDER BY ${orderBy} ${orderDirection} LIMIT ?, ? `; - try { - const follows = await db.queryAsync(query, params); - return follows.map(follow => formatFollow(follow)); - } catch (e: any) { - log.error(`[graphql] follows, ${JSON.stringify(e)}`); - capture(e, { args }); - return Promise.reject(new Error('request failed')); - } + const follows = await db.queryAsync(query, params); + return follows.map(follow => formatFollow(follow)); } diff --git a/src/graphql/operations/leaderboards.ts b/src/graphql/operations/leaderboards.ts index a9196ffb..9474bcb9 100644 --- a/src/graphql/operations/leaderboards.ts +++ b/src/graphql/operations/leaderboards.ts @@ -1,6 +1,4 @@ -import { capture } from '@snapshot-labs/snapshot-sentry'; import uniq from 'lodash/uniq'; -import log from '../../helpers/log'; import db from '../../helpers/mysql'; import { buildWhereQuery, checkLimits } from '../helpers'; @@ -50,11 +48,5 @@ export default async function (parent, args) { ORDER BY ${orderQuery} LIMIT ?, ? `; - try { - return db.queryAsync(query, [...whereQuery.params, skip, first]); - } catch (e) { - log.error(`[graphql] leaderboards, ${JSON.stringify(e)}`); - capture(e, { args }); - return Promise.reject(new Error('request failed')); - } + return db.queryAsync(query, [...whereQuery.params, skip, first]); } diff --git a/src/graphql/operations/messages.ts b/src/graphql/operations/messages.ts index 31747c59..dee06b16 100644 --- a/src/graphql/operations/messages.ts +++ b/src/graphql/operations/messages.ts @@ -1,5 +1,3 @@ -import { capture } from '@snapshot-labs/snapshot-sentry'; -import log from '../../helpers/log'; import { sequencerDB } from '../../helpers/mysql'; import { buildWhereQuery, checkLimits } from '../helpers'; @@ -33,11 +31,5 @@ export default async function (parent, args) { ORDER BY ${orderBy} ${orderDirection} LIMIT ?, ? `; params.push(skip, first); - try { - return await sequencerDB.queryAsync(query, params); - } catch (e: any) { - log.error(`[graphql] messages, ${JSON.stringify(e)}`); - capture(e, { args }); - return Promise.reject(new Error('request failed')); - } + return await sequencerDB.queryAsync(query, params); } diff --git a/src/graphql/operations/proposal.ts b/src/graphql/operations/proposal.ts index a54538d5..fa0db7d3 100644 --- a/src/graphql/operations/proposal.ts +++ b/src/graphql/operations/proposal.ts @@ -1,5 +1,3 @@ -import { capture } from '@snapshot-labs/snapshot-sentry'; -import log from '../../helpers/log'; import db from '../../helpers/mysql'; import { formatProposal } from '../helpers'; @@ -22,12 +20,6 @@ export default async function (parent, { id }) { WHERE p.id = ? AND spaces.settings IS NOT NULL LIMIT 1 `; - try { - const proposals = await db.queryAsync(query, [id]); - return proposals.map(proposal => formatProposal(proposal))[0] || null; - } catch (e: any) { - log.error(`[graphql] proposal, ${JSON.stringify(e)}`); - capture(e, { id }); - return Promise.reject(new Error('request failed')); - } + const proposals = await db.queryAsync(query, [id]); + return proposals.map(proposal => formatProposal(proposal))[0] || null; } diff --git a/src/graphql/operations/proposals.ts b/src/graphql/operations/proposals.ts index 57669a84..a0c849da 100644 --- a/src/graphql/operations/proposals.ts +++ b/src/graphql/operations/proposals.ts @@ -1,5 +1,3 @@ -import { capture } from '@snapshot-labs/snapshot-sentry'; -import log from '../../helpers/log'; import db from '../../helpers/mysql'; import { buildWhereQuery, checkLimits, formatProposal } from '../helpers'; @@ -109,12 +107,6 @@ export default async function (parent, args) { ORDER BY ${orderBy} ${orderDirection}, p.id ASC LIMIT ?, ? `; params.push(skip, first); - try { - const proposals = await db.queryAsync(query, params); - return proposals.map(proposal => formatProposal(proposal)); - } catch (e: any) { - log.error(`[graphql] proposals, ${JSON.stringify(e)}`); - capture(e, { args }); - return Promise.reject(new Error('request failed')); - } + const proposals = await db.queryAsync(query, params); + return proposals.map(proposal => formatProposal(proposal)); } diff --git a/src/graphql/operations/roles.ts b/src/graphql/operations/roles.ts index 35d9bdd0..0801634f 100644 --- a/src/graphql/operations/roles.ts +++ b/src/graphql/operations/roles.ts @@ -1,5 +1,3 @@ -import { capture } from '@snapshot-labs/snapshot-sentry'; -import log from '../../helpers/log'; import db from '../../helpers/mysql'; export default async function (parent, args) { @@ -13,31 +11,25 @@ export default async function (parent, args) { OR JSON_CONTAINS(LOWER(settings->'$.moderators'), LOWER(JSON_QUOTE(?))); `; - try { - const data = await db.queryAsync(query, [address, address, address]); - return data.map((space: any) => { - const settings = JSON.parse(space.settings); - const permissions: string[] = []; - const admins = (settings.admins || []).map((admin: string) => - admin.toLowerCase() - ); - const moderators = (settings.moderators || []).map((moderator: string) => - moderator.toLowerCase() - ); - const members = (settings.members || []).map((member: string) => - member.toLowerCase() - ); + const data = await db.queryAsync(query, [address, address, address]); + return data.map((space: any) => { + const settings = JSON.parse(space.settings); + const permissions: string[] = []; + const admins = (settings.admins || []).map((admin: string) => + admin.toLowerCase() + ); + const moderators = (settings.moderators || []).map((moderator: string) => + moderator.toLowerCase() + ); + const members = (settings.members || []).map((member: string) => + member.toLowerCase() + ); - if (admins.includes(address.toLowerCase())) permissions.push('admin'); - if (moderators.includes(address.toLowerCase())) - permissions.push('moderator'); - if (members.includes(address.toLowerCase())) permissions.push('author'); + if (admins.includes(address.toLowerCase())) permissions.push('admin'); + if (moderators.includes(address.toLowerCase())) + permissions.push('moderator'); + if (members.includes(address.toLowerCase())) permissions.push('author'); - return { space: space.id, permissions }; - }); - } catch (e: any) { - log.error(`[graphql] roles, ${JSON.stringify(e)}`); - capture(e, { args }); - return Promise.reject(new Error('request failed')); - } + return { space: space.id, permissions }; + }); } diff --git a/src/graphql/operations/statement.ts b/src/graphql/operations/statement.ts index 621279aa..dde01947 100644 --- a/src/graphql/operations/statement.ts +++ b/src/graphql/operations/statement.ts @@ -1,17 +1,9 @@ -import { capture } from '@snapshot-labs/snapshot-sentry'; -import log from '../../helpers/log'; import db from '../../helpers/mysql'; export default async function (parent, args) { const id = args.id; const query = `SELECT s.* FROM statements s WHERE id = ? LIMIT 1`; - try { - const statements = await db.queryAsync(query, id); - if (statements.length === 1) return statements[0]; - return null; - } catch (e: any) { - log.error(`[graphql] statement, ${JSON.stringify(e)}`); - capture(e, { args }); - return Promise.reject(new Error('request failed')); - } + const statements = await db.queryAsync(query, id); + if (statements.length === 1) return statements[0]; + return null; } diff --git a/src/graphql/operations/statements.ts b/src/graphql/operations/statements.ts index 72d2bfd9..3d5c157b 100644 --- a/src/graphql/operations/statements.ts +++ b/src/graphql/operations/statements.ts @@ -1,5 +1,3 @@ -import { capture } from '@snapshot-labs/snapshot-sentry'; -import log from '../../helpers/log'; import db from '../../helpers/mysql'; import { buildWhereQuery, checkLimits } from '../helpers'; @@ -36,12 +34,6 @@ export default async function (parent, args) { ORDER BY ${orderBy} ${orderDirection} LIMIT ?, ? `; params.push(skip, first); - try { - statements = await db.queryAsync(query, params); - return statements; - } catch (e: any) { - log.error(`[graphql] statements, ${JSON.stringify(e)}`); - capture(e, { args }); - return Promise.reject(new Error('request failed')); - } + statements = await db.queryAsync(query, params); + return statements; } diff --git a/src/graphql/operations/subscriptions.ts b/src/graphql/operations/subscriptions.ts index 6f5e1d2a..d3bbc4d5 100644 --- a/src/graphql/operations/subscriptions.ts +++ b/src/graphql/operations/subscriptions.ts @@ -1,5 +1,3 @@ -import { capture } from '@snapshot-labs/snapshot-sentry'; -import log from '../../helpers/log'; import db from '../../helpers/mysql'; import { buildWhereQuery, checkLimits, formatSubscription } from '../helpers'; @@ -48,12 +46,6 @@ export default async function (parent, args) { `; params.push(skip, first); - try { - subscriptions = await db.queryAsync(query, params); - return subscriptions.map(subscription => formatSubscription(subscription)); - } catch (e: any) { - log.error(`[graphql] subscriptions, ${JSON.stringify(e)}`); - capture(e, { args }); - return Promise.reject(new Error('request failed')); - } + subscriptions = await db.queryAsync(query, params); + return subscriptions.map(subscription => formatSubscription(subscription)); } diff --git a/src/graphql/operations/users.ts b/src/graphql/operations/users.ts index ef9414ff..c04e46e2 100644 --- a/src/graphql/operations/users.ts +++ b/src/graphql/operations/users.ts @@ -1,5 +1,3 @@ -import { capture } from '@snapshot-labs/snapshot-sentry'; -import log from '../../helpers/log'; import db from '../../helpers/mysql'; import { buildWhereQuery, checkLimits, formatUser } from '../helpers'; @@ -39,43 +37,37 @@ export default async function (parent, args) { ORDER BY ${orderBy} ${orderDirection} LIMIT ?, ? `; params.push(skip, first); - try { - const users = await db.queryAsync(query, params); - ids.forEach(element => { - if (!users.find((u: any) => u.id === element)) { - users.push({ id: element }); - } - }); - if (!users.length) return []; - - const usersWithOutCreated = users - .filter((u: any) => !u.created) - .map((u: any) => u.id); - if (usersWithOutCreated.length) { - const counts = await db.queryAsync( - ` - SELECT - user, - COALESCE(SUM(vote_count), 0) as votesCount, - COALESCE(SUM(proposal_count) ,0) as proposalsCount, - MAX(last_vote) as lastVote - FROM leaderboard - WHERE user IN (?) - GROUP BY user - `, - [usersWithOutCreated] - ); - counts.forEach((count: any) => { - const user = users.find((u: any) => u.id === count.user); - user.votesCount = count.votesCount; - user.proposalsCount = count.proposalsCount; - user.lastVote = count.lastVote; - }); + const users = await db.queryAsync(query, params); + ids.forEach(element => { + if (!users.find((u: any) => u.id === element)) { + users.push({ id: element }); } - return users.map(formatUser); - } catch (e: any) { - log.error(`[graphql] users, ${JSON.stringify(e)}`); - capture(e, { args }); - return Promise.reject(new Error('request failed')); + }); + if (!users.length) return []; + + const usersWithOutCreated = users + .filter((u: any) => !u.created) + .map((u: any) => u.id); + if (usersWithOutCreated.length) { + const counts = await db.queryAsync( + ` + SELECT + user, + COALESCE(SUM(vote_count), 0) as votesCount, + COALESCE(SUM(proposal_count) ,0) as proposalsCount, + MAX(last_vote) as lastVote + FROM leaderboard + WHERE user IN (?) + GROUP BY user + `, + [usersWithOutCreated] + ); + counts.forEach((count: any) => { + const user = users.find((u: any) => u.id === count.user); + user.votesCount = count.votesCount; + user.proposalsCount = count.proposalsCount; + user.lastVote = count.lastVote; + }); } + return users.map(formatUser); } diff --git a/src/graphql/operations/votes.ts b/src/graphql/operations/votes.ts index d8407a30..b3de163c 100644 --- a/src/graphql/operations/votes.ts +++ b/src/graphql/operations/votes.ts @@ -1,6 +1,4 @@ -import { capture } from '@snapshot-labs/snapshot-sentry'; import graphqlFields from 'graphql-fields'; -import log from '../../helpers/log'; import db from '../../helpers/mysql'; import serve from '../../helpers/requestDeduplicator'; import { @@ -48,15 +46,9 @@ async function query(parent, args, context?, info?) { ORDER BY ${orderBy} ${orderDirection}, v.id ASC LIMIT ?, ? `; params.push(skip, first); - try { - votes = await db.queryAsync(query, params); - // TODO: we need settings in the vote as its being passed to formatSpace inside formatVote, Maybe we dont need to do this? - votes = votes.map(vote => formatVote(vote)); - } catch (e: any) { - capture(e, { args, context, info }); - log.error(`[graphql] votes, ${JSON.stringify(e)}`); - return Promise.reject(new Error('request failed')); - } + votes = await db.queryAsync(query, params); + // TODO: we need settings in the vote as its being passed to formatSpace inside formatVote, Maybe we dont need to do this? + votes = votes.map(vote => formatVote(vote)); if (requestedFields.space && votes.length > 0) { const spaceIds = votes @@ -66,28 +58,22 @@ async function query(parent, args, context?, info?) { SELECT * FROM spaces WHERE id IN (?) AND settings IS NOT NULL AND deleted = 0 `; - try { - let spaces = await db.queryAsync(query, [spaceIds]); + let spaces = await db.queryAsync(query, [spaceIds]); - spaces = Object.fromEntries( - spaces.map(space => [ - space.id, - formatSpace({ - turboExpiration: space.turbo_expiration, - ...space - }) - ]) - ); - votes = votes.map(vote => { - if (spaces[vote.space.id]) - return { ...vote, space: spaces[vote.space.id] }; - return vote; - }); - } catch (e: any) { - capture(e, { args, context, info }); - log.error(`[graphql] votes, ${JSON.stringify(e)}`); - return Promise.reject(new Error('request failed')); - } + spaces = Object.fromEntries( + spaces.map(space => [ + space.id, + formatSpace({ + turboExpiration: space.turbo_expiration, + ...space + }) + ]) + ); + votes = votes.map(vote => { + if (spaces[vote.space.id]) + return { ...vote, space: spaces[vote.space.id] }; + return vote; + }); } if (requestedFields.proposal && votes.length > 0) { @@ -109,20 +95,14 @@ async function query(parent, args, context?, info?) { LEFT JOIN skins ON spaces.id = skins.id WHERE spaces.settings IS NOT NULL AND p.id IN (?) `; - try { - let proposals = await db.queryAsync(query, [proposalIds]); - proposals = Object.fromEntries( - proposals.map(proposal => [proposal.id, formatProposal(proposal)]) - ); - votes = votes.map(vote => { - vote.proposal = proposals[vote.proposal]; - return vote; - }); - } catch (e: any) { - capture(e, { args, context, info }); - log.error(`[graphql] votes, ${JSON.stringify(e)}`); - return Promise.reject(new Error('request failed')); - } + let proposals = await db.queryAsync(query, [proposalIds]); + proposals = Object.fromEntries( + proposals.map(proposal => [proposal.id, formatProposal(proposal)]) + ); + votes = votes.map(vote => { + vote.proposal = proposals[vote.proposal]; + return vote; + }); } return votes; diff --git a/src/helpers/mysql.ts b/src/helpers/mysql.ts index b8ab1192..cef823cd 100644 --- a/src/helpers/mysql.ts +++ b/src/helpers/mysql.ts @@ -1,3 +1,4 @@ +import { capture } from '@snapshot-labs/snapshot-sentry'; import bluebird from 'bluebird'; import parse from 'connection-string'; import mysql from 'mysql'; @@ -40,7 +41,19 @@ sequencerConfig.ssl = { const sequencerDB = mysql.createPool(sequencerConfig); -bluebird.promisifyAll([Pool, Connection]); +bluebird.promisifyAll([Pool, Connection], { + filter: name => name !== 'query' +}); + +Pool.prototype.queryAsync = function (...args: any[]) { + return bluebird + .fromCallback(cb => this.query(...args, cb)) + .catch(e => { + capture(e); + log.error(`[mysql] ${JSON.stringify(e)}`); + return Promise.reject(new Error('request failed')); + }); +}; export const closeDatabase = (): Promise => { return new Promise(resolve => { From d438e0ea971077bcc7ef98fad500a3d1bf37c9fb Mon Sep 17 00:00:00 2001 From: wa0x6e <495709+wa0x6e@users.noreply.github.com> Date: Sat, 14 Feb 2026 15:58:36 -0500 Subject: [PATCH 2/6] fix: filter out ER_QUERY_TIMEOUT errors from Sentry Co-Authored-By: Claude Opus 4.6 --- src/helpers/mysql.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/helpers/mysql.ts b/src/helpers/mysql.ts index cef823cd..4266ba1f 100644 --- a/src/helpers/mysql.ts +++ b/src/helpers/mysql.ts @@ -49,7 +49,7 @@ Pool.prototype.queryAsync = function (...args: any[]) { return bluebird .fromCallback(cb => this.query(...args, cb)) .catch(e => { - capture(e); + if (e.code !== 'ER_QUERY_TIMEOUT') capture(e); log.error(`[mysql] ${JSON.stringify(e)}`); return Promise.reject(new Error('request failed')); }); From f16f54d367c5fb6d1cff735472a312af22d498e7 Mon Sep 17 00:00:00 2001 From: wa0x6e <495709+wa0x6e@users.noreply.github.com> Date: Sat, 14 Feb 2026 16:15:21 -0500 Subject: [PATCH 3/6] fix: wrap all graphql resolvers to reject on any error Co-Authored-By: Claude Opus 4.6 --- src/graphql/operations/index.ts | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/src/graphql/operations/index.ts b/src/graphql/operations/index.ts index 1d79c9cb..51c252c1 100644 --- a/src/graphql/operations/index.ts +++ b/src/graphql/operations/index.ts @@ -24,7 +24,12 @@ import vote from './vote'; import votes from './votes'; import vp from './vp'; -export default { +function safe(fn) { + return (...args) => + fn(...args).catch(() => Promise.reject(new Error('request failed'))); +} + +const operations = { space, spaces, ranking, @@ -51,3 +56,7 @@ export default { roles, leaderboards }; + +export default Object.fromEntries( + Object.entries(operations).map(([key, fn]) => [key, safe(fn)]) +); From 846466ecdc86c3a7a6642095beac3696a00443b4 Mon Sep 17 00:00:00 2001 From: wa0x6e <495709+wa0x6e@users.noreply.github.com> Date: Sat, 14 Feb 2026 16:17:16 -0500 Subject: [PATCH 4/6] refactor: move error handling from mysql.ts to resolver wrapper Co-Authored-By: Claude Opus 4.6 --- src/graphql/operations/index.ts | 8 +++++++- src/helpers/mysql.ts | 15 +-------------- 2 files changed, 8 insertions(+), 15 deletions(-) diff --git a/src/graphql/operations/index.ts b/src/graphql/operations/index.ts index 51c252c1..b7456f71 100644 --- a/src/graphql/operations/index.ts +++ b/src/graphql/operations/index.ts @@ -1,3 +1,5 @@ +import { capture } from '@snapshot-labs/snapshot-sentry'; +import log from '../../helpers/log'; import aliases from './aliases'; import follows from './follows'; import leaderboards from './leaderboards'; @@ -26,7 +28,11 @@ import vp from './vp'; function safe(fn) { return (...args) => - fn(...args).catch(() => Promise.reject(new Error('request failed'))); + fn(...args).catch(e => { + if (e.code !== 'ER_QUERY_TIMEOUT') capture(e); + log.error(`[graphql] ${JSON.stringify(e)}`); + return Promise.reject(new Error('request failed')); + }); } const operations = { diff --git a/src/helpers/mysql.ts b/src/helpers/mysql.ts index 4266ba1f..b8ab1192 100644 --- a/src/helpers/mysql.ts +++ b/src/helpers/mysql.ts @@ -1,4 +1,3 @@ -import { capture } from '@snapshot-labs/snapshot-sentry'; import bluebird from 'bluebird'; import parse from 'connection-string'; import mysql from 'mysql'; @@ -41,19 +40,7 @@ sequencerConfig.ssl = { const sequencerDB = mysql.createPool(sequencerConfig); -bluebird.promisifyAll([Pool, Connection], { - filter: name => name !== 'query' -}); - -Pool.prototype.queryAsync = function (...args: any[]) { - return bluebird - .fromCallback(cb => this.query(...args, cb)) - .catch(e => { - if (e.code !== 'ER_QUERY_TIMEOUT') capture(e); - log.error(`[mysql] ${JSON.stringify(e)}`); - return Promise.reject(new Error('request failed')); - }); -}; +bluebird.promisifyAll([Pool, Connection]); export const closeDatabase = (): Promise => { return new Promise(resolve => { From 4f24b7f0dd2610ab99aa9452b07fdcf2c9aa9782 Mon Sep 17 00:00:00 2001 From: wa0x6e <495709+wa0x6e@users.noreply.github.com> Date: Sat, 14 Feb 2026 16:19:28 -0500 Subject: [PATCH 5/6] refactor: rename safe to withErrorHandler Co-Authored-By: Claude Opus 4.6 --- src/graphql/operations/index.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/graphql/operations/index.ts b/src/graphql/operations/index.ts index b7456f71..7e3c41ce 100644 --- a/src/graphql/operations/index.ts +++ b/src/graphql/operations/index.ts @@ -1,5 +1,4 @@ import { capture } from '@snapshot-labs/snapshot-sentry'; -import log from '../../helpers/log'; import aliases from './aliases'; import follows from './follows'; import leaderboards from './leaderboards'; @@ -25,8 +24,9 @@ import validations from './validations'; import vote from './vote'; import votes from './votes'; import vp from './vp'; +import log from '../../helpers/log'; -function safe(fn) { +function withErrorHandler(fn) { return (...args) => fn(...args).catch(e => { if (e.code !== 'ER_QUERY_TIMEOUT') capture(e); @@ -64,5 +64,5 @@ const operations = { }; export default Object.fromEntries( - Object.entries(operations).map(([key, fn]) => [key, safe(fn)]) + Object.entries(operations).map(([key, fn]) => [key, withErrorHandler(fn)]) ); From 13960472f7929481b07eb48ae88847338eade5cc Mon Sep 17 00:00:00 2001 From: wa0x6e <495709+wa0x6e@users.noreply.github.com> Date: Sat, 14 Feb 2026 16:19:57 -0500 Subject: [PATCH 6/6] refactor: extract ignored error codes to constant Co-Authored-By: Claude Opus 4.6 --- src/graphql/operations/index.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/graphql/operations/index.ts b/src/graphql/operations/index.ts index 7e3c41ce..37dbdf29 100644 --- a/src/graphql/operations/index.ts +++ b/src/graphql/operations/index.ts @@ -26,10 +26,12 @@ import votes from './votes'; import vp from './vp'; import log from '../../helpers/log'; +const IGNORED_ERROR_CODES = ['ER_QUERY_TIMEOUT']; + function withErrorHandler(fn) { return (...args) => fn(...args).catch(e => { - if (e.code !== 'ER_QUERY_TIMEOUT') capture(e); + if (!IGNORED_ERROR_CODES.includes(e.code)) capture(e); log.error(`[graphql] ${JSON.stringify(e)}`); return Promise.reject(new Error('request failed')); });