Token导航 LogoToken导航

Agent Skill · 前端设计

convex-anti-patterns(凸反图案)

用于辅助前端页面、组件、样式和交互逻辑的开发与维护。它适合让 Agent 生成或审查 React、Next.js、Vue、Tailwind、CSS 等相关代码,整理组件结构,或定位布局和性能问题。使用时需要结合项目现有设计系统、路由和构建方式,避免只生成孤立片段;涉及页面改动时,应配合本地预览和构建检查确认视觉效果。

convex-anti-patterns 是前端设计类 Agent Skill,可用宿主为 Codex、Claude、Cursor、Gemini CLI,通过GitHub 仓库获取,权限为调用外部服务;许可证为 MIT,第三方扫描已通过,按安装量在前端设计类的 10,216 个 Skill 里排第 3,040。

总安装 1,632周安装 66GitHub Stars 16下载 512核验 2026-05-01

安装说明

本站只整理中文说明,不托管安装包,也不代用户安装。
GitHub
许可证
MIT

安装方式

通过对话安装

复制提示词发给支持本地命令或 Skills 的 AI 助手,先确认命令和权限,再让它执行。

请帮我安装这个 Agent Skill:convex-anti-patterns(凸反图案)
来源仓库:https://github.com/fluid-tools/claude-skills
仓库路径:skills/convex-anti-patterns
安装命令:
npx skills add https://github.com/fluid-tools/claude-skills --skill convex-anti-patterns
安装前请先检查当前环境是否支持对应 CLI,并向我确认将要执行的命令、安装目录、联网范围和文件读写权限;确认后再执行。

命令行安装

复制命令到本机终端执行。不同来源提供的安装方式可能略有差异;本站展示可直接复制的安装命令,安装前请核对来源页面。

skills.shnpx skills
npx skills add https://github.com/fluid-tools/claude-skills --skill convex-anti-patterns

这个 Skill 是做什么的

前端设计类 Skill 关注页面结构、视觉规范和组件写法,输出通常是可运行的界面代码。

记录 Convex 开发中的常见错误模式和 Agent 必须遵守的规则。帮助避免 TypeScript any 类型滥用、函数注册不规范等导致的生产问题。涵盖 schema 定义、索引命名、函数语法等核心开发规范要求。作为开发辅助工具,不能替代人工代码审查和安全审计流程。

convex-anti-patterns 属于前端设计类 Skill,可作为该场景下的辅助能力补充。

来源仓库内路径:skills/convex-anti-patterns

上手步骤

4 步
  1. 1

    确认自己的宿主工具

    公开资料标注支持 Codex、Claude、Cursor、Gemini CLI;先确认自己用的是其中之一,再按对应工具的 Skills 目录放置。

  2. 2

    拿到安装命令

    可以直接复制的命令是 npx skills add https://github.com/fluid-tools/claude-skills --skill convex-anti-…;命令里的占位符换成自己的路径或密钥再执行。

  3. 3

    安装后重启宿主

    命令执行完要完全退出宿主工具再打开,让新装的 Skill 被重新扫描到,否则列表里可能看不到。

  4. 4

    按最小权限试用

    它需要调用外部服务,先在测试目录或测试账号里跑,确认没问题再放到正式项目。

常见问题

6 条

convex-anti-patterns 怎么安装?

在终端执行 npx skills add https://github.com/fluid-tools/claude-skills --skill convex-anti-patterns。装完完全退出宿主工具再打开,让它重新扫描本地 Skill。

convex-anti-patterns 支持哪些 AI 工具?

公开资料标注支持 Codex、Claude、Cursor、Gemini CLI。其他工具只要能读取本地 Skill 目录,一般也能手动放进去,但要自己确认触发方式是否一致。

convex-anti-patterns 需要什么权限?

权限级别是调用外部服务。该 Skill 可能调用第三方服务、云服务或外部模型 API,使用前需要确认账号、额度、数据发送范围和服务条款。

convex-anti-patterns 是免费的吗?

来源标注的许可证是 MIT。本站不代收任何费用,也不托管安装包;能否商用、要不要署名,以许可证原文为准。

convex-anti-patterns 用的人多吗?

公开数据是累计安装 1,632 次,近一周安装 66 次,下载 512 次,GitHub 16 星。这些数字来自第三方目录统计,只能当作热度参考,不代表质量或安全性。

convex-anti-patterns 安全吗?

第三方扫描结果为通过,但这只覆盖已知的静态检查项,不能替代自己读一遍源码和安装命令。

值得选的地方和要先想清楚的地方

值得选的地方

  • 同时适配 4 种工具,换客户端不用重新找替代品。
  • 3 个目录同时收录,说明这个 Skill 流传比较广。
  • 许可证明确为 MIT,商用和分发边界好判断。
  • 第三方扫描已通过,常见的高风险行为没有命中。

先想清楚的地方

  • 第三方目录整理,版本更新和行为细节以来源仓库为准。

适不适合你

更适合

  • 已经在用 Codex,想直接扩展它能力的人。
  • 需要处理前端设计类任务、又不想每次都手写提示词的人。
  • 需要许可证清楚、并且有第三方扫描结果的团队项目。

不太适合

  • 需要厂商级技术支持和 SLA 的生产系统。

前端设计类 Skill 对比

6 个同类
Skill总安装GitHub 星权限许可证扫描
frontend-design

前端设计 · Codex、Claude

8,823,360126,072只读MIT通过
vercel-composition-patterns

Vercel 组合模式 · Codex、Claude

3,858,81625,893需要联网MIT通过
vercel-react-native-skills

Vercel React native skills 前端 · Codex、Claude

2,468,66425,878只读MIT通过
adapt

adapt 搜索 · Codex、Claude

2,027,04023,125操作浏览器未明确通过
bolder

bolder 分析 · Codex、Claude

2,004,28823,130只读未明确通过
convex-anti-patterns

凸反图案 · 本页

1,63216调用外部服务MIT通过

按安装量取同分类前几名;数字来自第三方目录统计,只作热度参考,不代表质量结论。

SKILL.md 原文

Convex Anti-Patterns & Agent Rules

Overview

This skill documents critical mistakes to avoid in Convex development and rules that agents must follow. Every pattern here has caused real production issues.

TypeScript: NEVER Use any Type

CRITICAL RULE: This codebase has @typescript-eslint/no-explicit-any enabled. Using any will cause build failures.

❌ WRONG:

function handleData(data: any) { ... }
const items: any[] = [];
args: { data: v.any() }  // Also avoid!

✅ CORRECT:

function handleData(data: Doc<"items">) { ... }
const items: Doc<"items">[] = [];
args: { data: v.object({ field: v.string() }) }

When to Use This Skill

Use this skill when:

  • Reviewing Convex code for issues
  • Debugging mysterious errors
  • Understanding why code doesn't work as expected
  • Learning Convex best practices by counter-example
  • Checking code against known anti-patterns

Critical Anti-Patterns

Anti-Pattern 1: fetch() in Mutations

Mutations must be deterministic. External calls break this guarantee.

❌ WRONG:

export const createOrder = mutation({
  args: { productId: v.string() },
  returns: v.null(),
  handler: async (ctx, args) => {
    // ❌ Mutations cannot make external HTTP calls!
    const price = await fetch(
      `https://api.stripe.com/prices/${args.productId}`
    );
    await ctx.db.insert("orders", {
      productId: args.productId,
      price: await price.json(),
    });
    return null;
  },
});

✅ CORRECT:

// Mutation creates record, schedules action for external call
export const createOrder = mutation({
  args: { productId: v.string() },
  returns: v.id("orders"),
  handler: async (ctx, args) => {
    const orderId = await ctx.db.insert("orders", {
      productId: args.productId,
      status: "pending",
    });
    await ctx.scheduler.runAfter(0, internal.orders.fetchPrice, { orderId });
    return orderId;
  },
});

// Action handles external API call
export const fetchPrice = internalAction({
  args: { orderId: v.id("orders") },
  returns: v.null(),
  handler: async (ctx, args) => {
    const order = await ctx.runQuery(internal.orders.getById, {
      orderId: args.orderId,
    });
    if (!order) return null;

    const response = await fetch(
      `https://api.stripe.com/prices/${order.productId}`
    );
    const priceData = await response.json();

    await ctx.runMutation(internal.orders.updatePrice, {
      orderId: args.orderId,
      price: priceData.unit_amount,
    });
    return null;
  },
});

Anti-Pattern 2: ctx.db in Actions

Actions don't have database access. This is a common source of TypeScript errors.

❌ WRONG:

export const processData = action({
  args: { id: v.id("items") },
  returns: v.null(),
  handler: async (ctx, args) => {
    // ❌ Actions don't have ctx.db!
    const item = await ctx.db.get(args.id); // TypeScript Error!
    return null;
  },
});

✅ CORRECT:

export const processData = action({
  args: { id: v.id("items") },
  returns: v.null(),
  handler: async (ctx, args) => {
    // ✅ Use ctx.runQuery to read
    const item = await ctx.runQuery(internal.items.getById, { id: args.id });

    // Process with external APIs...
    const result = await fetch("https://api.example.com/process", {
      method: "POST",
      body: JSON.stringify(item),
    });

    // ✅ Use ctx.runMutation to write
    await ctx.runMutation(internal.items.updateResult, {
      id: args.id,
      result: await result.json(),
    });

    return null;
  },
});

Anti-Pattern 3: Missing returns Validator

Every function must have an explicit returns validator.

❌ WRONG:

export const doSomething = mutation({
  args: { data: v.string() },
  // ❌ Missing returns!
  handler: async (ctx, args) => {
    await ctx.db.insert("items", { data: args.data });
    // Implicitly returns undefined
  },
});

✅ CORRECT:

export const doSomething = mutation({
  args: { data: v.string() },
  returns: v.null(), // ✅ Explicit returns validator
  handler: async (ctx, args) => {
    await ctx.db.insert("items", { data: args.data });
    return null; // ✅ Explicit return value
  },
});

Anti-Pattern 4: Using.filter() on Queries

.filter() scans the entire table. Always use indexes.

❌ WRONG:

export const getActiveUsers = query({
  args: {},
  returns: v.array(v.object({ _id: v.id("users"), name: v.string() })),
  handler: async (ctx) => {
    // ❌ Full table scan!
    return await ctx.db
      .query("users")
      .filter((q) => q.eq(q.field("status"), "active"))
      .collect();
  },
});

✅ CORRECT:

// Schema: .index("by_status", ["status"])

export const getActiveUsers = query({
  args: {},
  returns: v.array(v.object({ _id: v.id("users"), name: v.string() })),
  handler: async (ctx) => {
    // ✅ Uses index
    return await ctx.db
      .query("users")
      .withIndex("by_status", (q) => q.eq("status", "active"))
      .collect();
  },
});

Anti-Pattern 5: Unbounded.collect()

Never collect without limits on potentially large tables.

❌ WRONG:

export const getAllMessages = query({
  args: { channelId: v.id("channels") },
  returns: v.array(v.object({ content: v.string() })),
  handler: async (ctx, args) => {
    // ❌ Could return millions of records!
    return await ctx.db
      .query("messages")
      .withIndex("by_channel", (q) => q.eq("channelId", args.channelId))
      .collect();
  },
});

✅ CORRECT:

export const getRecentMessages = query({
  args: { channelId: v.id("channels") },
  returns: v.array(v.object({ content: v.string() })),
  handler: async (ctx, args) => {
    // ✅ Bounded with take()
    return await ctx.db
      .query("messages")
      .withIndex("by_channel", (q) => q.eq("channelId", args.channelId))
      .order("desc")
      .take(50);
  },
});

Anti-Pattern 6:.collect().length for Counts

Collecting just to count is wasteful.

❌ WRONG:

export const getMessageCount = query({
  args: { channelId: v.id("channels") },
  returns: v.number(),
  handler: async (ctx, args) => {
    // ❌ Loads all messages just to count!
    const messages = await ctx.db
      .query("messages")
      .withIndex("by_channel", (q) => q.eq("channelId", args.channelId))
      .collect();
    return messages.length;
  },
});

✅ CORRECT:

// Option 1: Bounded count with "99+" display
export const getMessageCount = query({
  args: { channelId: v.id("channels") },
  returns: v.string(),
  handler: async (ctx, args) => {
    const messages = await ctx.db
      .query("messages")
      .withIndex("by_channel", (q) => q.eq("channelId", args.channelId))
      .take(100);
    return messages.length === 100 ? "99+" : String(messages.length);
  },
});

// Option 2: Denormalized counter (best for high traffic)
// Maintain messageCount field in channels table
export const getMessageCount = query({
  args: { channelId: v.id("channels") },
  returns: v.number(),
  handler: async (ctx, args) => {
    const channel = await ctx.db.get(args.channelId);
    return channel?.messageCount ?? 0;
  },
});

Anti-Pattern 7: N+1 Query Pattern

Loading related documents one by one.

❌ WRONG:

export const getPostsWithAuthors = query({
  args: {},
  returns: v.array(
    v.object({
      post: v.object({ title: v.string() }),
      author: v.object({ name: v.string() }),
    })
  ),
  handler: async (ctx) => {
    const posts = await ctx.db.query("posts").take(10);

    // ❌ N additional queries!
    const postsWithAuthors = await Promise.all(
      posts.map(async (post) => ({
        post: { title: post.title },
        author: await ctx.db
          .get(post.authorId)
          .then((a) => ({ name: a!.name })),
      }))
    );

    return postsWithAuthors;
  },
});

✅ CORRECT:

import { getAll } from "convex-helpers/server/relationships";

export const getPostsWithAuthors = query({
  args: {},
  returns: v.array(
    v.object({
      post: v.object({ title: v.string() }),
      author: v.union(v.object({ name: v.string() }), v.null()),
    })
  ),
  handler: async (ctx) => {
    const posts = await ctx.db.query("posts").take(10);

    // ✅ Batch fetch all authors
    const authorIds = [...new Set(posts.map((p) => p.authorId))];
    const authors = await getAll(ctx.db, authorIds);
    const authorMap = new Map(
      authors
        .filter((a): a is NonNullable<typeof a> => a !== null)
        .map((a) => [a._id, a])
    );

    return posts.map((post) => ({
      post: { title: post.title },
      author: authorMap.get(post.authorId)
        ? { name: authorMap.get(post.authorId)!.name }
        : null,
    }));
  },
});

Anti-Pattern 8: Global Counter (Hot Spot)

Single document updates cause OCC conflicts under load.

❌ WRONG:

export const incrementPageViews = mutation({
  args: {},
  returns: v.null(),
  handler: async (ctx) => {
    // ❌ Every request writes to same document!
    const stats = await ctx.db.query("globalStats").unique();
    await ctx.db.patch(stats!._id, { views: stats!.views + 1 });
    return null;
  },
});

✅ CORRECT:

// Option 1: Sharding
export const incrementPageViews = mutation({
  args: {},
  returns: v.null(),
  handler: async (ctx) => {
    // ✅ Write to random shard
    const shardId = Math.floor(Math.random() * 10);
    await ctx.db.insert("viewShards", { shardId, delta: 1 });
    return null;
  },
});

// Read by aggregating shards
export const getPageViews = query({
  args: {},
  returns: v.number(),
  handler: async (ctx) => {
    const shards = await ctx.db.query("viewShards").collect();
    return shards.reduce((sum, s) => sum + s.delta, 0);
  },
});

// Option 2: Use Workpool to serialize
import { Workpool } from "@convex-dev/workpool";

const counterPool = new Workpool(components.workpool, { maxParallelism: 1 });

export const incrementPageViews = mutation({
  args: {},
  returns: v.null(),
  handler: async (ctx) => {
    await counterPool.enqueueMutation(ctx, internal.stats.doIncrement, {});
    return null;
  },
});

Anti-Pattern 9: Using v.bigint() (Deprecated)

❌ WRONG:

export default defineSchema({
  counters: defineTable({
    value: v.bigint(), // ❌ Deprecated!
  }),
});

✅ CORRECT:

export default defineSchema({
  counters: defineTable({
    value: v.int64(), // ✅ Use v.int64()
  }),
});

Anti-Pattern 10: Missing System Fields in Return Validators

❌ WRONG:

export const getUser = query({
  args: { userId: v.id("users") },
  returns: v.object({
    // ❌ Missing _id and _creationTime!
    name: v.string(),
    email: v.string(),
  }),
  handler: async (ctx, args) => {
    return await ctx.db.get(args.userId); // Returns full doc including system fields
  },
});

✅ CORRECT:

export const getUser = query({
  args: { userId: v.id("users") },
  returns: v.union(
    v.object({
      _id: v.id("users"), // ✅ Include system fields
      _creationTime: v.number(),
      name: v.string(),
      email: v.string(),
    }),
    v.null()
  ),
  handler: async (ctx, args) => {
    return await ctx.db.get(args.userId);
  },
});

Anti-Pattern 11: Public Functions for Internal Logic

❌ WRONG:

// ❌ This is callable by any client!
export const deleteUserData = mutation({
  args: { userId: v.id("users") },
  returns: v.null(),
  handler: async (ctx, args) => {
    // Dangerous operation exposed publicly
    await ctx.db.delete(args.userId);
    return null;
  },
});

✅ CORRECT:

// Internal mutation - not callable by clients
export const deleteUserData = internalMutation({
  args: { userId: v.id("users") },
  returns: v.null(),
  handler: async (ctx, args) => {
    await ctx.db.delete(args.userId);
    return null;
  },
});

// Public mutation with auth check
export const requestAccountDeletion = mutation({
  args: {},
  returns: v.null(),
  handler: async (ctx) => {
    const identity = await ctx.auth.getUserIdentity();
    if (!identity) throw new Error("Unauthorized");

    const user = await ctx.db
      .query("users")
      .withIndex("by_token", (q) =>
        q.eq("tokenIdentifier", identity.tokenIdentifier)
      )
      .unique();

    if (!user) throw new Error("User not found");

    // Schedule internal mutation
    await ctx.scheduler.runAfter(0, internal.users.deleteUserData, {
      userId: user._id,
    });

    return null;
  },
});

Anti-Pattern 12: Non-Transactional Actions for Data Consistency

❌ WRONG:

export const transferFunds = action({
  args: { from: v.id("accounts"), to: v.id("accounts"), amount: v.number() },
  returns: v.null(),
  handler: async (ctx, args) => {
    // ❌ These are separate transactions - could leave inconsistent state!
    await ctx.runMutation(internal.accounts.debit, {
      accountId: args.from,
      amount: args.amount,
    });

    // If this fails, money was debited but not credited!
    await ctx.runMutation(internal.accounts.credit, {
      accountId: args.to,
      amount: args.amount,
    });

    return null;
  },
});

✅ CORRECT:

// Single atomic mutation
export const transferFunds = mutation({
  args: { from: v.id("accounts"), to: v.id("accounts"), amount: v.number() },
  returns: v.null(),
  handler: async (ctx, args) => {
    // ✅ All in one transaction - all succeed or all fail
    const fromAccount = await ctx.db.get(args.from);
    const toAccount = await ctx.db.get(args.to);

    if (!fromAccount || !toAccount) throw new Error("Account not found");
    if (fromAccount.balance < args.amount)
      throw new Error("Insufficient funds");

    await ctx.db.patch(args.from, {
      balance: fromAccount.balance - args.amount,
    });
    await ctx.db.patch(args.to, { balance: toAccount.balance + args.amount });

    return null;
  },
});

Anti-Pattern 13: Redundant Indexes

❌ WRONG:

export default defineSchema({
  messages: defineTable({
    channelId: v.id("channels"),
    authorId: v.id("users"),
    content: v.string(),
  })
    .index("by_channel", ["channelId"]) // ❌ Redundant!
    .index("by_channel_author", ["channelId", "authorId"]),
});

✅ CORRECT:

export default defineSchema({
  messages: defineTable({
    channelId: v.id("channels"),
    authorId: v.id("users"),
    content: v.string(),
  })
    // ✅ Single compound index serves both query patterns
    .index("by_channel_author", ["channelId", "authorId"]),
});

// Use prefix matching for channel-only queries:
// .withIndex("by_channel_author", (q) => q.eq("channelId", id))

Anti-Pattern 14: Using v.string() for IDs

❌ WRONG:

export const getMessage = query({
  args: { messageId: v.string() }, // ❌ Should be v.id()
  returns: v.null(),
  handler: async (ctx, args) => {
    // Type error or runtime error
    return await ctx.db.get(args.messageId as Id<"messages">);
  },
});

✅ CORRECT:

export const getMessage = query({
  args: { messageId: v.id("messages") }, // ✅ Proper ID type
  returns: v.union(
    v.object({
      _id: v.id("messages"),
      _creationTime: v.number(),
      content: v.string(),
    }),
    v.null()
  ),
  handler: async (ctx, args) => {
    return await ctx.db.get(args.messageId);
  },
});

Anti-Pattern 15: Retry Without Backoff or Jitter

❌ WRONG:

export const processWithRetry = internalAction({
  args: { jobId: v.id("jobs"), attempt: v.number() },
  returns: v.null(),
  handler: async (ctx, args) => {
    try {
      // Process...
    } catch (error) {
      if (args.attempt < 5) {
        // ❌ Fixed delay causes thundering herd!
        await ctx.scheduler.runAfter(5000, internal.jobs.processWithRetry, {
          jobId: args.jobId,
          attempt: args.attempt + 1,
        });
      }
    }
    return null;
  },
});

✅ CORRECT:

export const processWithRetry = internalAction({
  args: { jobId: v.id("jobs"), attempt: v.number() },
  returns: v.null(),
  handler: async (ctx, args) => {
    try {
      // Process...
    } catch (error) {
      if (args.attempt < 5) {
        // ✅ Exponential backoff + jitter
        const baseDelay = Math.pow(2, args.attempt) * 1000;
        const jitter = Math.random() * 1000;
        await ctx.scheduler.runAfter(
          baseDelay + jitter,
          internal.jobs.processWithRetry,
          {
            jobId: args.jobId,
            attempt: args.attempt + 1,
          }
        );
      }
    }
    return null;
  },
});

Agent Rules Summary

Must Do

  1. Always include returns validator on every function
  2. Always use indexes instead of .filter()
  3. Always use take(n) for potentially large queries
  4. Always use v.id("table") for document ID arguments
  5. Always use internalMutation/internalAction for sensitive operations
  6. Always handle errors in actions and update status in database
  7. Always use exponential backoff with jitter for retries

Must Not Do

  1. Never call fetch() in mutations
  2. Never access ctx.db in actions
  3. Never use .filter() on database queries
  4. Never use .collect() without limits on large tables
  5. Never use v.bigint() (deprecated, use v.int64())
  6. Never use any type (ESLint rule enforced)
  7. Never write to hot-spot documents without sharding/workpool
  8. Never expose dangerous operations as public functions
  9. Never rely on multiple mutations for atomic operations

Quick Checklist

Before submitting Convex code, verify:

  • All functions have returns validators
  • All queries use indexes (no .filter())
  • All .collect() calls are bounded with .take(n)
  • All ID arguments use v.id("tableName")
  • External API calls are in actions, not mutations
  • Actions use ctx.runQuery/ctx.runMutation for DB access
  • Sensitive operations use internal functions
  • No any types in the codebase
  • High-write documents use sharding or Workpool
  • Retries use exponential backoff with jitter

平台分布

Claude Code

30.48%
按下载量换算156

OpenCode

26.02%
按下载量换算133

Cursor

16.74%
按下载量换算86

Gemini CLI

11.83%
按下载量换算61

Antigravity

8.87%
按下载量换算45

Codex

3.35%
按下载量换算17

安全审计

Gen Agent Trust Hub

通过

Socket

通过

Snyk

通过

权限和风险

调用外部服务

该 Skill 可能调用第三方服务、云服务或外部模型 API,使用前需要确认账号、额度、数据发送范围和服务条款。

安装前确认

本站仅展示第三方公开信息,不托管安装包,不提供自动安装或运行环境。安装前应自行审查源码、依赖和命令行为。

继续浏览同类 Skills