koala73/worldmonitor · error · ConvexError
INVALID_COUNTRY
INVALID_COUNTRY
Error message
INVALID_COUNTRY
What it means
Validation sentinel in followCountry: the supplied country identifier is not a recognized or valid country code in the followed-countries domain. Typed as ConvexError with object data (kind 'INVALID_COUNTRY') so clients branch on err.data.kind.
Solutions
- Send the canonical country code from the country list the UI already renders
- Validate the code against the supported country set client-side before calling followCountry
Defensive patterns
Strategy: validation
When it happens
Trigger: Thrown at convex/followedCountries.ts:347 when the library encounters an invalid state.
Common situations: See trigger scenarios.
AI-assisted analysis of koala73/worldmonitor@eeab0a219f (2026-08-21).
Data as JSON: /api/errors/7919d31698302884.
Report an issue: GitHub.
Appendix: source
Thrown at convex/followedCountries.ts:347
*
* Without Tier 1, two parallel first-ever mutations could both read
* `meta=undefined`, both INSERT, and produce duplicate meta rows that
* break the `.unique()` read AND re-open the cap-bypass window. With
* Tier 1 in place, the brand-new-user race is closed deterministically.
*
* Errors are typed `ConvexError({kind, ...})` with object data so callers
* can branch on `err.data.kind` (memory:
* `convex-error-string-data-strips-errordata-on-wire`).
*/
export const followCountry = mutation({
args: { country: v.string() },
handler: async (ctx, args): Promise<FollowMutationResult> => {
const identity = await ctx.auth.getUserIdentity();
if (!identity) throw new ConvexError({ kind: "UNAUTHENTICATED" });
const userId = identity.subject;
if (!isValidIso2(args.country)) {
throw new ConvexError({
kind: "INVALID_COUNTRY",
country: args.country,
});
}
// Tier-1 lock: pre-seeded shard row. Read at top, patch at end.
const shard = await readShardOrThrow(ctx, userId);
// Tier-2 read: per-user denormalized count (lazy-created, but safe
// under the shard lock above).
const { meta, count: currentCount } = await readUserMeta(ctx, userId);
const existingRow = await ctx.db
.query("followedCountries")
.withIndex("by_user_country", (q) =>
q.eq("userId", userId).eq("country", args.country),
)
.first();View on GitHub (pinned to eeab0a219f)