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

  1. Send the canonical country code from the country list the UI already renders
  2. 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)