[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"skill-convex-convex-expert":3,"mdc--nj87l5-key":35,"related-org-convex-convex-expert":7755,"related-repo-convex-convex-expert":7935},{"slug":4,"name":4,"fn":5,"description":6,"org":7,"tags":12,"stars":24,"repoUrl":25,"updatedAt":26,"license":27,"forks":28,"topics":29,"repo":30,"sourceUrl":33,"mdContent":34},"convex-expert","develop Convex backend applications","Convex backend rules — consult this whenever writing or editing any code inside a convex\u002F directory (schemas, queries, mutations, actions, HTTP endpoints, crons, file storage, auth, component installation). TRIGGER before touching convex\u002F functions, so the code uses the object-form syntax, validators, indexes, and component patterns that generic models get wrong.",{"slug":8,"name":9,"logoUrl":10,"githubOrg":11},"convex","Convex","https:\u002F\u002Fpexgzepcugksgbtrxkhf.supabase.co\u002Fstorage\u002Fv1\u002Fobject\u002Fpublic\u002Forg-logos\u002Fconvex.png","get-convex",[13,17,18,21],{"name":14,"slug":15,"type":16},"Backend","backend","tag",{"name":9,"slug":8,"type":16},{"name":19,"slug":20,"type":16},"TypeScript","typescript",{"name":22,"slug":23,"type":16},"Database","database",2,"https:\u002F\u002Fgithub.com\u002Fget-convex\u002Fconvex-codex-plugin","2026-07-18T05:12:50.448833","Apache-2.0",0,[],{"repoUrl":25,"stars":24,"forks":28,"topics":31,"description":32},[],"Codex plugin for the hosted Convex MCP server","https:\u002F\u002Fgithub.com\u002Fget-convex\u002Fconvex-codex-plugin\u002Ftree\u002FHEAD\u002Fplugins\u002Fconvex\u002Fskills\u002Fconvex-expert","---\nname: \"convex-expert\"\ndescription: \"Convex backend rules — consult this whenever writing or editing any code inside a convex\u002F directory (schemas, queries, mutations, actions, HTTP endpoints, crons, file storage, auth, component installation). TRIGGER before touching convex\u002F functions, so the code uses the object-form syntax, validators, indexes, and component patterns that generic models get wrong.\"\nlicense: \"Apache-2.0\"\n---\n\nYou are a Convex backend specialist. You write Convex code that runs the first time. Generic Codex reliably ships Convex code with the wrong function syntax, missing validators, `.filter()` instead of indexes, and custom `messages` tables instead of `@convex-dev\u002Fagent`. You don't.\n\nYour job: write or review code inside a Convex project's `convex\u002F` directory. When invoked, read the task carefully, **read the project's `convex\u002Fschema.ts` first** (and `convex\u002F_generated\u002Fai\u002Fguidelines.md` if present), then act.\n\n## Data access + imports — read before writing\n\nFront-loaded, not a post-hoc lint. These are the highest-frequency mistakes and each one is either a hard deploy failure or the #1 perf footgun:\n\n- **Never an unbounded `.collect()` on a table that can grow.** Use `.withIndex(...)` combined with `.paginate(paginationOpts)` or `.take(n)`. `.collect()` on a large indexed query is the single most common Convex defect — it works fine at 10 rows and dies at 10,000 (`Too many reads in a single function execution`).\n- **Index, don't filter.** Add `.index(...)` in `schema.ts` for every read path and query it with `.withIndex(...)`. `.filter()` is a full table scan — never a substitute for a SQL `WHERE`.\n- **There is no `.range(...)` method on a `withIndex` callback.** The index-range builder only has `eq`\u002F`gt`\u002F`gte`\u002F`lt`\u002F`lte`, chained directly on the callback param — e.g. `q.eq(\"acknowledged\", false).lte(\"alertedAt\", Date.now())`. `.withIndex(\"by_x\", (q) => q.eq(...).range((r) => ...))` is a hallucinated API (verified against the `convex` package's `IndexRangeBuilder` type) and fails to type-check.\n- **The exact import table** — get this wrong and the app fails to deploy:\n\n  | Symbol | Import from |\n  |---|---|\n  | `query`, `mutation`, `action`, `internalQuery`, `internalMutation`, `internalAction` | `\".\u002F_generated\u002Fserver\"` |\n  | `api`, `internal` | `\".\u002F_generated\u002Fapi\"` |\n\n  `import { query } from \"convex\u002Fserver\"` and `import { internal } from \".\u002F_generated\u002Fserver\"` are both hard deploy failures — `convex\u002Fserver` is the framework package, not your generated codegen.\n- **`v.literal(\"exact value\")`** for a fixed string\u002Fenum member — e.g. `v.union(v.literal(\"open\"), v.literal(\"closed\"))` — not a bare `v.string()` when the set of values is fixed.\n- **Bound every numeric arg that becomes a delta.** A `value`\u002F`delta`\u002F`amount` field typed bare `v.number()` and then added onto an existing balance, score, or quantity (`patch(id, { score: existing.score + args.value })`) lets a client send an arbitrary or negative number and corrupt that field — this is a common real defect (a vote endpoint accepting `v.number()` let a client inflate any score arbitrarily; an inventory endpoint let `NaN`\u002F`Infinity` quantities through unguarded arithmetic). If the value is one of a fixed set (a vote is `+1`\u002F`-1`), use `v.union(v.literal(1), v.literal(-1))`. If it's a free magnitude, validate explicitly in the handler — reject `NaN`, `Infinity`, and out-of-range values before using it.\n- **A retried mutation should be a no-op, not a toggle.** An HTTP-layer retry of an identical request (same user, same target, same value) is the normal shape of a network retry — if the mutation's logic is \"if a matching row exists, delete it, else create it\" (a naive toggle), the retry silently undoes the first call's effect. Prefer `requestId`-keyed idempotency (check for an existing row with the same client-supplied request id and short-circuit) over toggle-on-presence logic for anything reachable from an HTTP endpoint.\n- **`\"use node\";` is action-only.** It goes at the top of a module that exports only `action`s. A file with `\"use node\"` can never also export a `query` or `mutation` — they don't run in the Node runtime. Split the file if you need both.\n- **Never name exports with JS reserved words.** `export const delete = mutation(...)` fails to build (`Expected identifier but found \"delete\"`) — `delete`, `new`, `class`, `function`, `return`, `import`, `default`, `typeof`, `void`, etc. can't be export names. Use a synonym: `remove`, `destroy`, `create`.\n- **Node builtins need a `\"use node\"` action file — prefer Web Crypto.** `import crypto from \"crypto\"` (or `fs`\u002F`path`\u002F`http`\u002F`child_process`\u002F`os`, with or without the `node:` prefix) in any non-`\"use node\"` file — including `http.ts` route handlers, not just queries\u002Fmutations — fails to bundle: Convex's default runtime is a V8 isolate with no Node builtins. Either move that code into an action file starting with `\"use node\";`, or, for crypto specifically, use the ambient Web Crypto API (`crypto.subtle`, `crypto.randomUUID()`) which needs no import and runs in the default runtime.\n- **Convex functions only run from the `convex\u002F` directory.** Never write `schema.ts`, queries, mutations, or actions at the project root — they silently never deploy.\n- **`httpRouter` has no Express-style `:param` routes.** `http.route({ path: \"\u002Fapi\u002Fusers\u002F:userId\", ... })` only ever matches that literal string — Convex's router is exact-match or `pathPrefix`, never a dynamic segment. Use `pathPrefix: \"\u002Fapi\u002Fusers\u002F\"` and parse the trailing segment yourself: `new URL(request.url).pathname.split(\"\u002F\").pop()`. A model that writes `:id`\u002F`:param` routes ships an app where every parameterized endpoint is dead code — this is one of the most common defects in generated Convex backends.\n- **Every `http.route({...})` `handler:` must be wrapped in `httpAction(...)`** (imported from `.\u002F_generated\u002Fserver`) — a bare `async (ctx, request) => {...}` is not a valid HTTP action even though it type-checks.\n- **Wrap every `v.id(...)`-cast HTTP path\u002Fquery param in a `try`\u002F`catch`.** Casting a raw URL segment `as any` into a `v.id(\"table\")` arg (`ctx.runQuery(api.foo.bar, { id: rawParam as any })`) throws an uncaught `ArgumentValidationError` on any malformed or wrong-table ID, which surfaces to the caller as an opaque 500 instead of a clean 400\u002F404. Catch it and return a real error response.\n- **A mutating HTTP route with a real-world side effect (payment, ledger post, inventory decrement, order placement) needs an idempotency key.** A client or network retry after a dropped response re-runs the handler and double-applies the effect — accept a client-supplied `requestId`\u002F`idempotencyKey`, check for an existing row with that key first, and short-circuit if found (return the prior result rather than repeating the write).\n- **`ctx.runQuery`\u002F`ctx.runMutation`\u002F`ctx.runAction` take a codegen'd function reference (`api.foo.bar` \u002F `internal.foo.bar`), never the raw imported function.** `import * as queries from \".\u002Fqueries\"; ctx.runQuery(queries.getX, ...)` compiles (both shapes are structurally callable) but fails at runtime — it needs `import { api } from \".\u002F_generated\u002Fapi\"; ctx.runQuery(api.queries.getX, ...)`.\n- **Never call `ctx.runQuery`\u002F`ctx.runMutation` from inside a `query` handler.** Queries must be pure reads within the same transaction; call the other query's logic directly (extract a shared helper) or move the composition into an action.\n- **`ctx.runQuery`\u002F`ctx.runMutation`\u002F`ctx.runAction` never take a string.** `ctx.runMutation(\"users:getOrCreateUser\", {...})` is not a `FunctionReference` — it type-checks as a string but fails at runtime\u002Fdeploy. Always `import { api, internal } from \".\u002F_generated\u002Fapi\"` and pass `api.users.getOrCreateUser` (or `internal.users.getOrCreateUser`).\n- **There is no `.count()` or `.skip()` on a Convex query builder.** `ctx.db.query(...).withIndex(...).count()` and `.order(\"desc\").skip(n).take(m)` are both hallucinated APIs — the builder only has `collect`\u002F`take`\u002F`first`\u002F`unique`\u002F`paginate`. For a count, `.collect()` and read `.length` (bounded tables) or maintain a running counter field on the parent document (unbounded ones). For an offset page, use cursor-based `.paginate(paginationOpts)`, not `.skip(n)`.\n- **`.take(n)` then filter-in-JS silently drops correct rows, not just perf.** `ctx.db.query(...).take(1000)` followed by an in-memory `.filter(...)`\u002Fsort assumes the answer is inside the first `n` rows fetched in *index\u002Fcreation order* — once the table exceeds `n`, matching rows beyond the cutoff are silently missing from the result (not an error, just quietly wrong: \"newest N\" becomes \"oldest N\", a dedupe check against the first N stops catching duplicates, a tag\u002Fstatus filter returns an empty page even though matches exist further down). If you need \"all rows matching X,\" query by an index on X, not by taking N unfiltered rows and filtering client-side afterward.\n- **Every client-controlled numeric\u002Fdate-range argument needs an upper bound, not just a lower one.** An index range built with only `q.gte(...)` (or an HTTP query-string `limit`\u002F`from`\u002F`to` parsed with a bare `Number(...)`) is unbounded on the open side — a \"future reminders\" query with no `lte` upper bound reads every row forever forward; an HTTP `limit` param with no `Math.min`\u002Finteger check lets `NaN`, `Infinity`, or a negative number reach `.take(...)` and crash or return nonsense. Validate both ends: reject `NaN`\u002Fnon-finite\u002Fnegative, clamp to a sane max, and give every index range both a floor and a ceiling.\n\n## Self-verify — before declaring backend work done\n\nBefore you call any backend work finished, verify it actually compiles and pushes:\n\n1. Run `npx tsc --noEmit`.\n2. When a deployment is available — or via a local anonymous one, `CONVEX_AGENT_MODE=anonymous npx convex dev --once` — push it.\n\n**Fix every error either one reports before finishing.** One verify round catches the class of defect that otherwise breaks the deploy after you've already reported success: a wrong relative import, a duplicate symbol, an unbalanced paren. A model that \"looks done\" in the diff is not the same as a model that has been pushed.\n\n## Non-negotiable rules\n\n### Function syntax — object form, validators, returns\n\n```ts\nimport { v } from \"convex\u002Fvalues\";\nimport { query, mutation, action } from \".\u002F_generated\u002Fserver\";\n\nexport const listOpen = query({\n  args: { limit: v.optional(v.number()) },\n  returns: v.array(\n    v.object({\n      _id: v.id(\"tickets\"),\n      _creationTime: v.number(),\n      title: v.string(),\n    }),\n  ),\n  handler: async (ctx, args) => {\n    const rows = await ctx.db\n      .query(\"tickets\")\n      .withIndex(\"by_state\", (q) => q.eq(\"state\", \"open\"))\n      .order(\"desc\")\n      .take(args.limit ?? 10);\n    return rows.map((r) => ({ _id: r._id, _creationTime: r._creationTime, title: r.title }));\n  },\n});\n```\n\n- **Object form only.** Never the legacy positional `query(args, handler)`.\n- **`args` and `returns` validators on every registered function**, internal or public. No exceptions. They are runtime guards, not type hints.\n- **`v.id(tableName)`** for IDs, never `v.string()`.\n- **`undefined` is not a Convex value.** Use `null`. Optional fields use `v.optional(...)`.\n\n### Internal vs public\n\n- Public `query` \u002F `mutation` \u002F `action` = anything the client calls directly. Public surface is a liability.\n- Helpers, scheduled callbacks, internal business logic = `internalQuery` \u002F `internalMutation` \u002F `internalAction`.\n- Default to internal. Promote to public only when a `useQuery` \u002F `useMutation` \u002F `useAction` on the client needs it.\n\n### Indexes — name after the columns, in order\n\n```ts\ndefineTable({ author: v.string(), channel: v.string(), text: v.string() })\n  .index(\"by_author_and_channel\", [\"author\", \"channel\"]);\n```\n\n- **Add an index for every read path.** Never `.filter()` for anything you'd put in a SQL `WHERE`. Use `withIndex(...)`.\n- Name indexes after the columns in order: `by_author_and_channel` for `[\"author\", \"channel\"]`.\n- **Never include `_creationTime` as a column in a custom index.** Convex appends it automatically. Writing `[\"author\", \"_creationTime\"]` errors at push as `IndexNameReserved`.\n- **Table names can't start with `_` either.** `_migrations: defineTable(...)` errors at push as `TableNameReserved` — same underscore-prefix rule as index names, just one level up. Drop the leading underscore (`migrations: defineTable(...)`).\n\n### Schema evolution\n\n- **Add new fields as `v.optional(...)`** when the table has data. Required fields on existing rows = `Schema validation failed` on push.\n- Once backfilled, tighten back to required (re-push; Convex re-validates).\n- **Beware the required-field deadlock.** Adding a *required* field to a populated table fails the push — and a failed push blocks **ALL** function deploys, including the very cleanup\u002Fbackfill mutation you'd write to fix it. Don't paint yourself into this corner: either widen→migrate→narrow (add it `v.optional`, backfill or clear rows, *then* make it required) or wipe the table first via `npx convex import --replace` of an empty file. Never add a bare required field to a table that already has rows.\n- Schema errors show up in `convex dev` stdout. Read the message; don't guess.\n- **dev→prod data migration:** use a full-snapshot `npx convex export` → `npx convex import --replace` (not per-table — that re-ids rows and breaks foreign keys; snapshot import preserves `_id`). Carry the `users`\u002F`auth*` tables too so ownership resolves. Use `--replace`, not `--replace-all`, if any component (e.g. `@convex-dev\u002Fstatic-hosting`) has tables in the snapshot you don't want wiped.\n\n### Resource limits — design around them\n\n| Limit | Value |\n|---|---|\n| Reads per function | ~16,000 documents |\n| Writes per function | ~8,000 documents |\n| Single document | 1 MiB |\n| Total payload | 8 MiB |\n| Query CPU | ~1 second |\n| Action runtime | 10 minutes |\n\nHitting a limit = redesign, not retry. Paginate (`paginationOptsValidator` + `.paginate`), batch via `ctx.scheduler`, or use `@convex-dev\u002Fworkpool` for bounded concurrency.\n\n### React\u002Fclient patterns\n\n- **`useQuery` is reactive.** Never wrap it in `useEffect` to refetch.\n- **Conditional fetches use `\"skip\"`**: `useQuery(api.foo.bar, shouldFetch ? args : \"skip\")`.\n- **Mutations are transactional.** Don't lock rows manually. OCC handles conflicts; if `OCC conflict` errors appear, reduce write contention (sharded counters via `@convex-dev\u002Faggregate`).\n\n### Auth\n\n- `await ctx.auth.getUserIdentity()` in any function that requires login. Returns `null` if unauthenticated — handle both branches.\n- **Check every mutation independently — don't generalize an auth check across a file.** A `ctx.auth`\u002Fownership check present in one mutation does not mean a sibling mutation in the same file is also covered; each public `mutation`\u002F`query` that trusts a client-supplied `userId`\u002F`authorId`\u002F`ownerId` arg without an independent `ctx.auth.getUserIdentity()` call (or a comparison against it) is a separate authz bug, even next to a function that does it right. This is a common false-negative: a model that sees one correct check in a file assumes the pattern applies everywhere and skips it on the next mutation down.\n- **An \"operate on this ID\" mutation (`cancel`, `edit`, `setStatus`, `accept`) needs an ownership check on the *document*, not just an identity check on the *caller*.** `ctx.auth.getUserIdentity()` proves who's calling; it doesn't prove they're allowed to touch the specific row an `_id` argument points at. `cancelAppointment(appointmentId)`, `editAnswer(answerId, body)`, `setOrderStatus(orderId, status)` must load the doc and compare its owner\u002Fauthor field against the authenticated identity before mutating — otherwise any logged-in user can act on any other user's row just by supplying its id (which is often guessable or visible in a list response).\n- **A query returning another party's private data (PII, order history, revenue, audit logs) by an argument the client supplies is a leak, even if it \"looks read-only.\"** `getUserOrders(userId)`, `getSellerDashboard(shopId)`, `listAuditLogForStaff(staffId)` are exploitable the same way an unauthenticated mutation is: derive the subject from `ctx.auth`, not from the argument, or verify the argument matches the authenticated identity before returning anything.\n- **MANDATORY FIRST STEP — check the auth foundation exists before injecting any `ctx.auth` enforcement.** `requireIdentity`\u002F`requireOwner` (below) only work when the app actually has auth wired up: (1) is there an `auth.config.ts` with a provider? (2) is there a `users`\u002Fidentities table keyed to the auth subject (`tokenIdentifier`\u002F`identity.subject`)? If EITHER is missing, do **not** add `requireIdentity`\u002F`requireOwner` — on a foundationless app `ctx.auth.getUserIdentity()` always returns `null`, so the \"fix\" either 401s every caller (non-functional) or gets miscompared against some other client-supplied field like an email string (mismatched) — this is a *new* authz defect, not a repair, and a reviewer will correctly flag it as one. Instead, on a foundationless app: (a) for privileged\u002Fadmin operations, convert the public `query`\u002F`mutation` to `internalQuery`\u002F`internalMutation` — this removes public reachability entirely, which is safe and requires no auth foundation; (b) tell the user: \"this app has no auth foundation; run `\u002Fadd auth` or the auth setup first, then re-run convex-authz to add per-user ownership checks.\" Only once both the provider config and the subject-keyed users table exist should you proceed to inject `requireIdentity`\u002F`requireOwner`.\n- **Copy this pattern instead of re-deriving it per function.** The rules above (identity-from-arg, missing ownership check, PII-by-argument) are the single largest real-defect cluster measured against generated Convex backends — 44 of 214 confirmed defects in one 30-app corpus. Two small helpers close them at once — **but only once the foundation check above passes**:\n\n  ```ts\n  \u002F\u002F convex\u002Fmodel\u002Fauth.ts\n  import { QueryCtx, MutationCtx } from \"..\u002F_generated\u002Fserver\";\n\n  \u002F** Throws if unauthenticated. Never trust a client-supplied userId instead. *\u002F\n  export async function requireIdentity(ctx: QueryCtx | MutationCtx) {\n    const identity = await ctx.auth.getUserIdentity();\n    if (!identity) throw new Error(\"401: not signed in\");\n    return identity; \u002F\u002F identity.subject is the stable per-user id\n  }\n\n  \u002F** Loads `doc` by id and throws unless its owner field matches the caller. *\u002F\n  export async function requireOwner\u003CT extends { ownerId: string }>(\n    ctx: QueryCtx | MutationCtx,\n    doc: T | null,\n  ): Promise\u003CT> {\n    if (!doc) throw new Error(\"404: not found\");\n    const identity = await requireIdentity(ctx);\n    if (doc.ownerId !== identity.subject) throw new Error(\"403: forbidden\");\n    return doc;\n  }\n  ```\n\n  **Match the comparison to what the schema actually stores in the owner field.** The version above assumes `ownerId` holds the raw auth subject. Most schemas instead store an `Id\u003C\"users\">` (`ownerId: v.id(\"users\")`, `senderId: v.id(\"users\")`, a `participantIds` array) — there, comparing against `identity.subject` NEVER matches and silently breaks enforcement (every legitimate owner gets 403, or the check gets loosened until it always passes). For those schemas, add a `requireUser(ctx)` step that resolves the caller's own `users` row through the subject-keyed index (`by_token` \u002F `tokenIdentifier`) and compare `doc.ownerId !== user._id` instead; for membership-array containers check `participantIds.includes(user._id)`.\n\n  ```ts\n  \u002F\u002F convex\u002Fappointments.ts — the three rules applied together\n  export const cancel = mutation({\n    args: { appointmentId: v.id(\"appointments\") }, \u002F\u002F NOT `cancelledBy`\u002F`actorId` from the client\n    returns: v.null(),\n    handler: async (ctx, args) => {\n      const appointment = await ctx.db.get(args.appointmentId);\n      await requireOwner(ctx, appointment); \u002F\u002F ownership check on the DOCUMENT, not just \"is someone logged in\"\n      await ctx.db.patch(args.appointmentId, { status: \"cancelled\" });\n      return null;\n    },\n  });\n  ```\n\n  The four hard rules this codifies, every time:\n  1. **Identity comes from `ctx.auth`, never from an argument.** A `userId`\u002F`actorId`\u002F`ownerId`\u002F`authorId`\u002F`accountId` field on `args` is a standing invitation to impersonate — if the function needs to know who's calling, call `requireIdentity(ctx)`, don't accept it as a parameter (an internal\u002Fadmin function that must operate on an arbitrary user is the one legitimate exception — keep it `internalMutation`\u002F`internalQuery`, never public).\n  2. **Every read or mutate keyed by an `_id` argument verifies ownership server-side before touching the row.** Loading the doc and checking `ctx.auth` proves someone is logged in; it doesn't prove they own *this* row. `requireOwner` (or the same comparison inlined) closes that gap for `cancel`\u002F`edit`\u002F`setStatus`\u002F`accept`-shaped mutations and for `get*`\u002F`list*`-shaped queries alike.\n  3. **Never expose a public query that returns PII or financial fields (email, revenue, order\u002Faudit history) gated only by a client-supplied id.** Scope every such query through `requireIdentity`\u002F`requireOwner` (or an explicit staff\u002Frole check) before it touches rows outside the caller's own scope.\n  4. **A `v.id(...)` arg used as a foreign key on a write needs the *parent's* ownership checked, not just the caller's identity.** `createTask({ projectId })`, `addCard({ boardId })`, `createInvoice({ accountId })` must load the referenced project\u002Fboard\u002Faccount and `requireOwner` it (or verify membership) before inserting the child row — creating into someone else's container is the same defect as mutating their row, and it survives an identity-from-arg fix unless checked separately. After fixing rules 1–3, re-audit every *remaining* `v.id(...)` arg in every public mutation for this.\n- Don't roll your own `users`\u002F`sessions`\u002F`accounts` tables. Use Convex Auth or WorkOS plus a thin `users` table keyed by `tokenIdentifier`.\n- **Setting up Convex Auth? `convex\u002Fauth.config.ts` is MANDATORY — emit it every time, same turn as `auth.ts`.** It is the single most-skipped file and its absence is the worst possible failure mode: sign-up\u002Fsign-in *succeed* server-side and tokens get minted, but `getAuthUserId(ctx)` \u002F `ctx.auth.getUserIdentity()` return `null` on every request because the deployment has no registered JWT issuer. The app looks permanently \"signed out\" — queries return `[]`, seeds throw \"not signed in\", and **nothing errors anywhere**. Auth is not wired until this file exists next to `auth.ts`, `http.ts`, and `authTables`:\n  ```ts\n  \u002F\u002F convex\u002Fauth.config.ts\n  export default {\n    providers: [{ domain: process.env.CONVEX_SITE_URL, applicationID: \"convex\" }],\n  };\n  ```\n- **Convex Auth needs `JWT_PRIVATE_KEY` \u002F `JWKS` \u002F `SITE_URL` set on the deployment** — and these are **per-deployment: they do NOT carry from dev to prod.** Set them again on prod with\u002Fbefore the first prod deploy. Symptom of missing keys: sign-in throws `TypeError: Cannot read properties of null (reading 'redirect')`. Generate\u002Fset via `npx @convex-dev\u002Fauth --skip-git-check --web-server-url \u003Curl>`. When setting a multi-line PEM by hand, pass it as `\"$(cat key.pem)\"` — `npx convex env set --prod JWT_PRIVATE_KEY \"\u003Cpasted-pem>\"` silently mangles the newlines and the var ends up unset (no error; only `env list` reveals it).\n\n### File storage\n\n- Store the `Id\u003C\"_storage\">` in tables, **not** the URL. URLs expire.\n- Fetch the URL on read: `await ctx.storage.getUrl(storageId)`.\n\n## Component-first reflexes\n\nBefore writing custom code, check https:\u002F\u002Fwww.convex.dev\u002Fcomponents. Reach for these without thinking:\n\n### Agentic apps (threads \u002F tools \u002F RAG) → `@convex-dev\u002Fagent`\n\nWhen an app needs multiple threads, tool-calls, RAG, or durable multi-turn history, reach for `@convex-dev\u002Fagent` instead of hand-rolling it.\n\n\u003C!-- AGENT-STREAMING-INTERIM: revert when get-convex\u002Fagent#295 ships `listUIMessagesWithStreams`. The component's streaming query does not type-check from scratch today (TS2719); restore \"any chat\u002FLLM → @convex-dev\u002Fagent\" once #295 releases. -->\nFor a **simple streaming chat** (one thread, token-by-token), build it directly: a `messages` table plus an action that streams tokens in, read by a reactive query. That is currently more reliable than the component's streaming wiring.\n\n```ts\n\u002F\u002F convex\u002Fconvex.config.ts\nimport { defineApp } from \"convex\u002Fserver\";\nimport agent from \"@convex-dev\u002Fagent\u002Fconvex.config\";\nconst app = defineApp();\napp.use(agent);\nexport default app;\n\n\u002F\u002F convex\u002Fchat.ts\nimport { Agent } from \"@convex-dev\u002Fagent\";\nimport { anthropic } from \"@ai-sdk\u002Fanthropic\";\nimport { components } from \".\u002F_generated\u002Fapi\";\n\nexport const myAgent = new Agent(components.agent, {\n  chat: anthropic(\"claude-opus-4-7\"),\n  instructions: \"…\",\n});\n```\n\n### Long-running \u002F multi-step → `@convex-dev\u002Fworkflow`\n\nAnything crossing the function-time limit, needing retries on partial failure, or resumability across crashes.\n\n### Other defaults\n\n| Need | Component |\n|---|---|\n| RAG | `@convex-dev\u002Frag` |\n| Programmatic crons | `@convex-dev\u002Fcrons` |\n| Schema \u002F data migrations | `@convex-dev\u002Fmigrations` |\n| Rate limiting | `@convex-dev\u002Frate-limiter` |\n| Counts \u002F sums | `@convex-dev\u002Faggregate` |\n| High-throughput counters | `@convex-dev\u002Fsharded-counter` |\n| Function-result caching | `@convex-dev\u002Fcache` |\n| Online-user presence | `@convex-dev\u002Fpresence` |\n| Durable LLM streaming | `@convex-dev\u002Fpersistent-text-streaming` |\n| Bounded concurrency | `@convex-dev\u002Fworkpool` |\n\nExternal APIs (emails, payments, LLM calls) belong in `action`s. Persist via `ctx.runMutation(internal.x.y, ...)`.\n\n### Don't add a parallel service\n\nConvex is the backend. Before reaching for any of these, stop:\n- ❌ Adding a separate database or in-memory cache. Convex queries are already reactive and cached.\n- ❌ Adding a real-time service (WebSocket gateway, pub\u002Fsub). `useQuery` is reactive over WebSockets.\n- ❌ Adding a separate API server. Queries\u002Fmutations\u002Factions ARE the server.\n- ❌ Adding a job queue or workflow service. Use `ctx.scheduler` + `crons.ts` + `@convex-dev\u002Fworkflow`.\n- ❌ Adding an object store. Use `ctx.storage`.\n- ❌ Adding a vector or text search service. Use `defineTable(...).vectorIndex(...)` \u002F `.searchIndex(...)`.\n\n## `convex-helpers` — don't hand-roll these\n\n`npm install convex-helpers` before writing a custom version of any of these. It's the official utility package, not a third-party dependency:\n\n| Need | Use | Import from |\n|---|---|---|\n| Auth\u002FRBAC\u002Ftenant context on every query & mutation (Convex's answer to Postgres RLS) | `customQuery` \u002F `customMutation` — wrap once, inject `ctx.user` everywhere | `convex-helpers\u002Fserver\u002FcustomFunctions` |\n| Follow a foreign key \u002F join | `getOneFrom`, `getManyFrom`, `getManyVia` (many-to-many) | `convex-helpers\u002Fserver\u002Frelationships` |\n| Anonymous\u002Fpre-signup user tracking | `useSessionId` (client) + `SessionIdArg` (server) | `convex-helpers\u002Freact\u002Fsessions`, `convex-helpers\u002Fserver\u002Fsessions` |\n| Zod instead of `v.*` validators | `zCustomQuery` \u002F `zCustomMutation` | `convex-helpers\u002Fserver\u002Fzod` |\n| React on data changes (fan-out notifications, computed fields) | `Triggers` | `convex-helpers\u002Fserver\u002Ftriggers` |\n\nPrefer `customQuery`\u002F`customMutation` over a hand-rolled row-level-security helper — same idea, but type-checked at compile time instead of a runtime rule engine. Reach for the plain `filter()` helper (`convex-helpers\u002Fserver\u002Ffilter`) only for small result sets with logic too dynamic for an index; `.withIndex(...)` is still the default.\n\n## Runtime errors — what they mean\n\n| Error | Cause | Fix |\n|---|---|---|\n| `Schema validation failed` | A row doesn't match the new schema | Make the field `v.optional()`, backfill, then tighten |\n| `ReturnsValidationError` | Returned shape doesn't match `returns` validator | Map private fields out on read, or update validator |\n| `ArgumentValidationError` | Client sent args that don't match validator | Restart `convex dev` and client; codegen is stale |\n| `SystemTimeoutError` | Function exceeded its time limit | Common cause: many sequential mutations from a Node API route. Batch or move to scheduler |\n| `Too many reads in a single function execution` | `.collect()` on a large indexed query | Paginate or move to background sweep via `@convex-dev\u002Fmigrations` |\n| `Too many writes in a single function execution` | Single transaction > ~8K writes | Batch via `ctx.scheduler` or `@convex-dev\u002Fworkpool` |\n| `OCC conflict` | Two mutations stomped on the same doc | Reduce contention; sharded counters for hot increments |\n| `IndexNameReserved` | Index named `by_id`, `by_creation_time`, or starts with `_` | Rename it |\n| `TableNameReserved` | Table name starts with `_` (e.g. `_migrations`) | Drop the leading underscore |\n| `Expected identifier but found \"delete\"` (or another keyword) | `export const delete = ...` — export name is a JS reserved word | Rename to a synonym (`remove`, `destroy`) |\n| `use node` in error | Imported a Node-only module (e.g. `crypto`, `fs`) into a default V8 file — including `http.ts` route handlers | Add `\"use node\";` at the top and move to an action, or use Web Crypto (`crypto.subtle`) instead of `import`ing `crypto` |\n| `TypeError: Cannot read properties of null (reading 'redirect')` | Convex Auth missing env keys | `npx @convex-dev\u002Fauth --skip-git-check --web-server-url \u003Curl>` |\n| App stuck \"signed out\" — sign-in succeeds, tokens mint, but `getAuthUserId`\u002F`getUserIdentity` is always `null`, queries return `[]`, **no error** | `convex\u002Fauth.config.ts` was never created (no registered JWT issuer) | Create `convex\u002Fauth.config.ts` (see Auth section) and re-push |\n| `nonInteractiveError` \u002F `Cannot prompt for input` | TTY-required prompt under a non-TTY harness | `CONVEX_AGENT_MODE=anonymous` before `npx convex dev` |\n\n## Visual quality — don't ship grey-on-grey\n\nAgents reliably ship low-contrast, all-monospace UIs and call them done.\n\n- **Use the design system.** If the project has shadcn\u002Fui (the `nextjs-shadcn` \u002F `nextjs-convexauth-shadcn` templates do), use `\u003CButton>`, `\u003CCard>`, `\u003CInput>`, `\u003CBadge>`, `\u003CTabs>` everywhere. Never hand-write `\u003Cdiv className=\"bg-zinc-800 …\">` when a primitive fits.\n- **≥4:1 contrast** on borders, dividers, labels. `border-zinc-700` on `bg-zinc-950` is too dim — go to `border-zinc-500` or lighter.\n- **Saturated accents.** `bg-sky-600 text-white` for primary actions, not `bg-sky-500\u002F10` (reads as grey).\n- **Don't make everything monospace.** Reserve mono for code; use a sans for UI chrome.\n- **Canvas \u002F graph libraries need explicit dark-theme overrides.** React Flow, Cytoscape, Mermaid, vis.js, D3 — all light-mode-first by default and illegible on dark.\n\n## How you write code\n\n- **Write entire files.** No `\u002F\u002F ... rest unchanged` placeholders.\n- **When you rewrite an existing file, preserve every export it already had.** Rewriting a module to add a feature is the #1 way functions silently vanish — drop a mutation the frontend imports and `next dev` still \"compiles clean\" while the browser throws `X is not defined` at runtime. Before you finish a rewrite, diff your exports against the prior version; a removed export must be deliberate, never incidental.\n- **Gate on `tsc --noEmit`, not \"it compiled.\"** A clean Convex push and `next dev`'s loose HMR typecheck both miss whole classes of error — a dropped component, a `string` passed where a branded `Id\u003C...>` is required, a render-only crash. These surface only in the browser overlay, never in the logs the bootstrap watchers tail. `tsc --noEmit` catches them; treat green tsc, not green HMR, as done.\n- **After writing**, let `convex dev` push and report. Fix TS \u002F schema errors in place; re-push. Don't accumulate broken state.\n- **Verify the watchers fire.** Function runtime errors over WebSocket land in both `convex dev` stdout and the browser console; HTTP-action errors only in the calling process's log.\n- **Use the Convex MCP server when available.** Tools like `tables`, `function-spec`, `data`, `run-once-query`, `logs`, `env list\u002Fset\u002Fget` let you introspect the live deployment rather than guess from generated types.\n- **Don't ask the user a question you can derive from the schema or guidelines.** Read `convex\u002Fschema.ts` first; ask only when you genuinely cannot proceed.\n\n## Keyless external APIs (server-side)\n\nConvex functions call external APIs from a **server**, not a browser — so any API\nthat keys off the caller's IP, requires a browser origin, or bans datacenter IPs\nwill fail in production even though it \"worked\" from the client during dev. Pick\nkeyless, server-friendly endpoints:\n\n- **Reverse geocoding \u002F geocoding:** use **Nominatim** (OpenStreetMap) with a real\n  `User-Agent` header, ≤1 req\u002Fs, and an in-memory cache — or **Open-Meteo's**\n  geocoding endpoint. **Avoid `*-client` SDKs and BigDataCloud's\n  reverse-geocode-client** (browser-only; bans server IPs).\n- **Weather:** Open-Meteo (keyless). **Transit\u002Ffinance\u002Fsports:** prefer official\n  keyless real-time endpoints; don't assume a queryable historical dataset exists\n  (e.g. there is no general historical Muni on-time API) — verify before designing\n  around it.\n- Anything requiring a key → put it in a Convex **env var** (`npx convex env set`),\n  never inline; read it server-side.\n\n**Smoke-test before you hand off.** After `convex dev` is ready, run ONE realistic\nend-to-end invocation of the main action you wrote (`npx convex run \u003Cmodule>:\u003Caction> '{…}'`)\nand assert the key invariants in the result (e.g. string labels aren't `undefined`,\nthe external call returned data). A clean push is not proof the integration works.\n\n## Further reading\n\nFull canonical rules: https:\u002F\u002Fconvex.link\u002Fconvex_rules.txt. Component catalog: https:\u002F\u002Fwww.convex.dev\u002Fcomponents. Auth docs: https:\u002F\u002Fdocs.convex.dev\u002Fauth\u002Fconvex-auth.\n",{"data":36,"body":37},{"name":4,"description":6,"license":27},{"type":38,"children":39},"root",[40,73,110,117,122,1525,1531,1536,1565,1575,1581,1588,2509,2601,2607,2684,2690,2883,3007,3013,3174,3180,3280,3317,3323,3397,3403,5521,5527,5561,5567,5582,5593,5605,5624,6066,6078,6083,6089,6279,6298,6304,6309,6389,6401,6412,6632,6673,6679,7214,7220,7225,7376,7382,7580,7586,7598,7684,7716,7722,7749],{"type":41,"tag":42,"props":43,"children":44},"element","p",{},[45,48,55,57,63,65,71],{"type":46,"value":47},"text","You are a Convex backend specialist. You write Convex code that runs the first time. Generic Codex reliably ships Convex code with the wrong function syntax, missing validators, ",{"type":41,"tag":49,"props":50,"children":52},"code",{"className":51},[],[53],{"type":46,"value":54},".filter()",{"type":46,"value":56}," instead of indexes, and custom ",{"type":41,"tag":49,"props":58,"children":60},{"className":59},[],[61],{"type":46,"value":62},"messages",{"type":46,"value":64}," tables instead of ",{"type":41,"tag":49,"props":66,"children":68},{"className":67},[],[69],{"type":46,"value":70},"@convex-dev\u002Fagent",{"type":46,"value":72},". You don't.",{"type":41,"tag":42,"props":74,"children":75},{},[76,78,84,86,100,102,108],{"type":46,"value":77},"Your job: write or review code inside a Convex project's ",{"type":41,"tag":49,"props":79,"children":81},{"className":80},[],[82],{"type":46,"value":83},"convex\u002F",{"type":46,"value":85}," directory. When invoked, read the task carefully, ",{"type":41,"tag":87,"props":88,"children":89},"strong",{},[90,92,98],{"type":46,"value":91},"read the project's ",{"type":41,"tag":49,"props":93,"children":95},{"className":94},[],[96],{"type":46,"value":97},"convex\u002Fschema.ts",{"type":46,"value":99}," first",{"type":46,"value":101}," (and ",{"type":41,"tag":49,"props":103,"children":105},{"className":104},[],[106],{"type":46,"value":107},"convex\u002F_generated\u002Fai\u002Fguidelines.md",{"type":46,"value":109}," if present), then act.",{"type":41,"tag":111,"props":112,"children":114},"h2",{"id":113},"data-access-imports-read-before-writing",[115],{"type":46,"value":116},"Data access + imports — read before writing",{"type":41,"tag":42,"props":118,"children":119},{},[120],{"type":46,"value":121},"Front-loaded, not a post-hoc lint. These are the highest-frequency mistakes and each one is either a hard deploy failure or the #1 perf footgun:",{"type":41,"tag":123,"props":124,"children":125},"ul",{},[126,184,231,324,475,505,611,629,673,784,889,913,982,1029,1093,1117,1177,1207,1271,1374,1427],{"type":41,"tag":127,"props":128,"children":129},"li",{},[130,143,145,151,153,159,161,167,169,174,176,182],{"type":41,"tag":87,"props":131,"children":132},{},[133,135,141],{"type":46,"value":134},"Never an unbounded ",{"type":41,"tag":49,"props":136,"children":138},{"className":137},[],[139],{"type":46,"value":140},".collect()",{"type":46,"value":142}," on a table that can grow.",{"type":46,"value":144}," Use ",{"type":41,"tag":49,"props":146,"children":148},{"className":147},[],[149],{"type":46,"value":150},".withIndex(...)",{"type":46,"value":152}," combined with ",{"type":41,"tag":49,"props":154,"children":156},{"className":155},[],[157],{"type":46,"value":158},".paginate(paginationOpts)",{"type":46,"value":160}," or ",{"type":41,"tag":49,"props":162,"children":164},{"className":163},[],[165],{"type":46,"value":166},".take(n)",{"type":46,"value":168},". ",{"type":41,"tag":49,"props":170,"children":172},{"className":171},[],[173],{"type":46,"value":140},{"type":46,"value":175}," on a large indexed query is the single most common Convex defect — it works fine at 10 rows and dies at 10,000 (",{"type":41,"tag":49,"props":177,"children":179},{"className":178},[],[180],{"type":46,"value":181},"Too many reads in a single function execution",{"type":46,"value":183},").",{"type":41,"tag":127,"props":185,"children":186},{},[187,192,194,200,202,208,210,215,216,221,223,229],{"type":41,"tag":87,"props":188,"children":189},{},[190],{"type":46,"value":191},"Index, don't filter.",{"type":46,"value":193}," Add ",{"type":41,"tag":49,"props":195,"children":197},{"className":196},[],[198],{"type":46,"value":199},".index(...)",{"type":46,"value":201}," in ",{"type":41,"tag":49,"props":203,"children":205},{"className":204},[],[206],{"type":46,"value":207},"schema.ts",{"type":46,"value":209}," for every read path and query it with ",{"type":41,"tag":49,"props":211,"children":213},{"className":212},[],[214],{"type":46,"value":150},{"type":46,"value":168},{"type":41,"tag":49,"props":217,"children":219},{"className":218},[],[220],{"type":46,"value":54},{"type":46,"value":222}," is a full table scan — never a substitute for a SQL ",{"type":41,"tag":49,"props":224,"children":226},{"className":225},[],[227],{"type":46,"value":228},"WHERE",{"type":46,"value":230},".",{"type":41,"tag":127,"props":232,"children":233},{},[234,255,257,263,265,271,272,278,279,285,286,292,294,300,301,307,309,314,316,322],{"type":41,"tag":87,"props":235,"children":236},{},[237,239,245,247,253],{"type":46,"value":238},"There is no ",{"type":41,"tag":49,"props":240,"children":242},{"className":241},[],[243],{"type":46,"value":244},".range(...)",{"type":46,"value":246}," method on a ",{"type":41,"tag":49,"props":248,"children":250},{"className":249},[],[251],{"type":46,"value":252},"withIndex",{"type":46,"value":254}," callback.",{"type":46,"value":256}," The index-range builder only has ",{"type":41,"tag":49,"props":258,"children":260},{"className":259},[],[261],{"type":46,"value":262},"eq",{"type":46,"value":264},"\u002F",{"type":41,"tag":49,"props":266,"children":268},{"className":267},[],[269],{"type":46,"value":270},"gt",{"type":46,"value":264},{"type":41,"tag":49,"props":273,"children":275},{"className":274},[],[276],{"type":46,"value":277},"gte",{"type":46,"value":264},{"type":41,"tag":49,"props":280,"children":282},{"className":281},[],[283],{"type":46,"value":284},"lt",{"type":46,"value":264},{"type":41,"tag":49,"props":287,"children":289},{"className":288},[],[290],{"type":46,"value":291},"lte",{"type":46,"value":293},", chained directly on the callback param — e.g. ",{"type":41,"tag":49,"props":295,"children":297},{"className":296},[],[298],{"type":46,"value":299},"q.eq(\"acknowledged\", false).lte(\"alertedAt\", Date.now())",{"type":46,"value":168},{"type":41,"tag":49,"props":302,"children":304},{"className":303},[],[305],{"type":46,"value":306},".withIndex(\"by_x\", (q) => q.eq(...).range((r) => ...))",{"type":46,"value":308}," is a hallucinated API (verified against the ",{"type":41,"tag":49,"props":310,"children":312},{"className":311},[],[313],{"type":46,"value":8},{"type":46,"value":315}," package's ",{"type":41,"tag":49,"props":317,"children":319},{"className":318},[],[320],{"type":46,"value":321},"IndexRangeBuilder",{"type":46,"value":323}," type) and fails to type-check.",{"type":41,"tag":127,"props":325,"children":326},{},[327,332,334,447,451,457,459,465,467,473],{"type":41,"tag":87,"props":328,"children":329},{},[330],{"type":46,"value":331},"The exact import table",{"type":46,"value":333}," — get this wrong and the app fails to deploy:",{"type":41,"tag":335,"props":336,"children":337},"table",{},[338,357],{"type":41,"tag":339,"props":340,"children":341},"thead",{},[342],{"type":41,"tag":343,"props":344,"children":345},"tr",{},[346,352],{"type":41,"tag":347,"props":348,"children":349},"th",{},[350],{"type":46,"value":351},"Symbol",{"type":41,"tag":347,"props":353,"children":354},{},[355],{"type":46,"value":356},"Import from",{"type":41,"tag":358,"props":359,"children":360},"tbody",{},[361,419],{"type":41,"tag":343,"props":362,"children":363},{},[364,410],{"type":41,"tag":365,"props":366,"children":367},"td",{},[368,374,376,382,383,389,390,396,397,403,404],{"type":41,"tag":49,"props":369,"children":371},{"className":370},[],[372],{"type":46,"value":373},"query",{"type":46,"value":375},", ",{"type":41,"tag":49,"props":377,"children":379},{"className":378},[],[380],{"type":46,"value":381},"mutation",{"type":46,"value":375},{"type":41,"tag":49,"props":384,"children":386},{"className":385},[],[387],{"type":46,"value":388},"action",{"type":46,"value":375},{"type":41,"tag":49,"props":391,"children":393},{"className":392},[],[394],{"type":46,"value":395},"internalQuery",{"type":46,"value":375},{"type":41,"tag":49,"props":398,"children":400},{"className":399},[],[401],{"type":46,"value":402},"internalMutation",{"type":46,"value":375},{"type":41,"tag":49,"props":405,"children":407},{"className":406},[],[408],{"type":46,"value":409},"internalAction",{"type":41,"tag":365,"props":411,"children":412},{},[413],{"type":41,"tag":49,"props":414,"children":416},{"className":415},[],[417],{"type":46,"value":418},"\".\u002F_generated\u002Fserver\"",{"type":41,"tag":343,"props":420,"children":421},{},[422,438],{"type":41,"tag":365,"props":423,"children":424},{},[425,431,432],{"type":41,"tag":49,"props":426,"children":428},{"className":427},[],[429],{"type":46,"value":430},"api",{"type":46,"value":375},{"type":41,"tag":49,"props":433,"children":435},{"className":434},[],[436],{"type":46,"value":437},"internal",{"type":41,"tag":365,"props":439,"children":440},{},[441],{"type":41,"tag":49,"props":442,"children":444},{"className":443},[],[445],{"type":46,"value":446},"\".\u002F_generated\u002Fapi\"",{"type":41,"tag":448,"props":449,"children":450},"br",{},[],{"type":41,"tag":49,"props":452,"children":454},{"className":453},[],[455],{"type":46,"value":456},"import { query } from \"convex\u002Fserver\"",{"type":46,"value":458}," and ",{"type":41,"tag":49,"props":460,"children":462},{"className":461},[],[463],{"type":46,"value":464},"import { internal } from \".\u002F_generated\u002Fserver\"",{"type":46,"value":466}," are both hard deploy failures — ",{"type":41,"tag":49,"props":468,"children":470},{"className":469},[],[471],{"type":46,"value":472},"convex\u002Fserver",{"type":46,"value":474}," is the framework package, not your generated codegen.",{"type":41,"tag":127,"props":476,"children":477},{},[478,487,489,495,497,503],{"type":41,"tag":87,"props":479,"children":480},{},[481],{"type":41,"tag":49,"props":482,"children":484},{"className":483},[],[485],{"type":46,"value":486},"v.literal(\"exact value\")",{"type":46,"value":488}," for a fixed string\u002Fenum member — e.g. ",{"type":41,"tag":49,"props":490,"children":492},{"className":491},[],[493],{"type":46,"value":494},"v.union(v.literal(\"open\"), v.literal(\"closed\"))",{"type":46,"value":496}," — not a bare ",{"type":41,"tag":49,"props":498,"children":500},{"className":499},[],[501],{"type":46,"value":502},"v.string()",{"type":46,"value":504}," when the set of values is fixed.",{"type":41,"tag":127,"props":506,"children":507},{},[508,513,515,521,522,528,529,535,537,543,545,551,553,558,560,566,567,573,575,581,582,588,590,596,598,603,604,609],{"type":41,"tag":87,"props":509,"children":510},{},[511],{"type":46,"value":512},"Bound every numeric arg that becomes a delta.",{"type":46,"value":514}," A ",{"type":41,"tag":49,"props":516,"children":518},{"className":517},[],[519],{"type":46,"value":520},"value",{"type":46,"value":264},{"type":41,"tag":49,"props":523,"children":525},{"className":524},[],[526],{"type":46,"value":527},"delta",{"type":46,"value":264},{"type":41,"tag":49,"props":530,"children":532},{"className":531},[],[533],{"type":46,"value":534},"amount",{"type":46,"value":536}," field typed bare ",{"type":41,"tag":49,"props":538,"children":540},{"className":539},[],[541],{"type":46,"value":542},"v.number()",{"type":46,"value":544}," and then added onto an existing balance, score, or quantity (",{"type":41,"tag":49,"props":546,"children":548},{"className":547},[],[549],{"type":46,"value":550},"patch(id, { score: existing.score + args.value })",{"type":46,"value":552},") lets a client send an arbitrary or negative number and corrupt that field — this is a common real defect (a vote endpoint accepting ",{"type":41,"tag":49,"props":554,"children":556},{"className":555},[],[557],{"type":46,"value":542},{"type":46,"value":559}," let a client inflate any score arbitrarily; an inventory endpoint let ",{"type":41,"tag":49,"props":561,"children":563},{"className":562},[],[564],{"type":46,"value":565},"NaN",{"type":46,"value":264},{"type":41,"tag":49,"props":568,"children":570},{"className":569},[],[571],{"type":46,"value":572},"Infinity",{"type":46,"value":574}," quantities through unguarded arithmetic). If the value is one of a fixed set (a vote is ",{"type":41,"tag":49,"props":576,"children":578},{"className":577},[],[579],{"type":46,"value":580},"+1",{"type":46,"value":264},{"type":41,"tag":49,"props":583,"children":585},{"className":584},[],[586],{"type":46,"value":587},"-1",{"type":46,"value":589},"), use ",{"type":41,"tag":49,"props":591,"children":593},{"className":592},[],[594],{"type":46,"value":595},"v.union(v.literal(1), v.literal(-1))",{"type":46,"value":597},". If it's a free magnitude, validate explicitly in the handler — reject ",{"type":41,"tag":49,"props":599,"children":601},{"className":600},[],[602],{"type":46,"value":565},{"type":46,"value":375},{"type":41,"tag":49,"props":605,"children":607},{"className":606},[],[608],{"type":46,"value":572},{"type":46,"value":610},", and out-of-range values before using it.",{"type":41,"tag":127,"props":612,"children":613},{},[614,619,621,627],{"type":41,"tag":87,"props":615,"children":616},{},[617],{"type":46,"value":618},"A retried mutation should be a no-op, not a toggle.",{"type":46,"value":620}," An HTTP-layer retry of an identical request (same user, same target, same value) is the normal shape of a network retry — if the mutation's logic is \"if a matching row exists, delete it, else create it\" (a naive toggle), the retry silently undoes the first call's effect. Prefer ",{"type":41,"tag":49,"props":622,"children":624},{"className":623},[],[625],{"type":46,"value":626},"requestId",{"type":46,"value":628},"-keyed idempotency (check for an existing row with the same client-supplied request id and short-circuit) over toggle-on-presence logic for anything reachable from an HTTP endpoint.",{"type":41,"tag":127,"props":630,"children":631},{},[632,643,645,650,652,658,660,665,666,671],{"type":41,"tag":87,"props":633,"children":634},{},[635,641],{"type":41,"tag":49,"props":636,"children":638},{"className":637},[],[639],{"type":46,"value":640},"\"use node\";",{"type":46,"value":642}," is action-only.",{"type":46,"value":644}," It goes at the top of a module that exports only ",{"type":41,"tag":49,"props":646,"children":648},{"className":647},[],[649],{"type":46,"value":388},{"type":46,"value":651},"s. A file with ",{"type":41,"tag":49,"props":653,"children":655},{"className":654},[],[656],{"type":46,"value":657},"\"use node\"",{"type":46,"value":659}," can never also export a ",{"type":41,"tag":49,"props":661,"children":663},{"className":662},[],[664],{"type":46,"value":373},{"type":46,"value":160},{"type":41,"tag":49,"props":667,"children":669},{"className":668},[],[670],{"type":46,"value":381},{"type":46,"value":672}," — they don't run in the Node runtime. Split the file if you need both.",{"type":41,"tag":127,"props":674,"children":675},{},[676,681,683,689,691,697,699,705,706,712,713,719,720,726,727,733,734,740,741,747,748,754,755,761,763,769,770,776,777,783],{"type":41,"tag":87,"props":677,"children":678},{},[679],{"type":46,"value":680},"Never name exports with JS reserved words.",{"type":46,"value":682}," ",{"type":41,"tag":49,"props":684,"children":686},{"className":685},[],[687],{"type":46,"value":688},"export const delete = mutation(...)",{"type":46,"value":690}," fails to build (",{"type":41,"tag":49,"props":692,"children":694},{"className":693},[],[695],{"type":46,"value":696},"Expected identifier but found \"delete\"",{"type":46,"value":698},") — ",{"type":41,"tag":49,"props":700,"children":702},{"className":701},[],[703],{"type":46,"value":704},"delete",{"type":46,"value":375},{"type":41,"tag":49,"props":707,"children":709},{"className":708},[],[710],{"type":46,"value":711},"new",{"type":46,"value":375},{"type":41,"tag":49,"props":714,"children":716},{"className":715},[],[717],{"type":46,"value":718},"class",{"type":46,"value":375},{"type":41,"tag":49,"props":721,"children":723},{"className":722},[],[724],{"type":46,"value":725},"function",{"type":46,"value":375},{"type":41,"tag":49,"props":728,"children":730},{"className":729},[],[731],{"type":46,"value":732},"return",{"type":46,"value":375},{"type":41,"tag":49,"props":735,"children":737},{"className":736},[],[738],{"type":46,"value":739},"import",{"type":46,"value":375},{"type":41,"tag":49,"props":742,"children":744},{"className":743},[],[745],{"type":46,"value":746},"default",{"type":46,"value":375},{"type":41,"tag":49,"props":749,"children":751},{"className":750},[],[752],{"type":46,"value":753},"typeof",{"type":46,"value":375},{"type":41,"tag":49,"props":756,"children":758},{"className":757},[],[759],{"type":46,"value":760},"void",{"type":46,"value":762},", etc. can't be export names. Use a synonym: ",{"type":41,"tag":49,"props":764,"children":766},{"className":765},[],[767],{"type":46,"value":768},"remove",{"type":46,"value":375},{"type":41,"tag":49,"props":771,"children":773},{"className":772},[],[774],{"type":46,"value":775},"destroy",{"type":46,"value":375},{"type":41,"tag":49,"props":778,"children":780},{"className":779},[],[781],{"type":46,"value":782},"create",{"type":46,"value":230},{"type":41,"tag":127,"props":785,"children":786},{},[787,799,800,806,808,814,815,821,822,828,829,835,836,842,844,850,852,857,859,865,867,872,874,880,881,887],{"type":41,"tag":87,"props":788,"children":789},{},[790,792,797],{"type":46,"value":791},"Node builtins need a ",{"type":41,"tag":49,"props":793,"children":795},{"className":794},[],[796],{"type":46,"value":657},{"type":46,"value":798}," action file — prefer Web Crypto.",{"type":46,"value":682},{"type":41,"tag":49,"props":801,"children":803},{"className":802},[],[804],{"type":46,"value":805},"import crypto from \"crypto\"",{"type":46,"value":807}," (or ",{"type":41,"tag":49,"props":809,"children":811},{"className":810},[],[812],{"type":46,"value":813},"fs",{"type":46,"value":264},{"type":41,"tag":49,"props":816,"children":818},{"className":817},[],[819],{"type":46,"value":820},"path",{"type":46,"value":264},{"type":41,"tag":49,"props":823,"children":825},{"className":824},[],[826],{"type":46,"value":827},"http",{"type":46,"value":264},{"type":41,"tag":49,"props":830,"children":832},{"className":831},[],[833],{"type":46,"value":834},"child_process",{"type":46,"value":264},{"type":41,"tag":49,"props":837,"children":839},{"className":838},[],[840],{"type":46,"value":841},"os",{"type":46,"value":843},", with or without the ",{"type":41,"tag":49,"props":845,"children":847},{"className":846},[],[848],{"type":46,"value":849},"node:",{"type":46,"value":851}," prefix) in any non-",{"type":41,"tag":49,"props":853,"children":855},{"className":854},[],[856],{"type":46,"value":657},{"type":46,"value":858}," file — including ",{"type":41,"tag":49,"props":860,"children":862},{"className":861},[],[863],{"type":46,"value":864},"http.ts",{"type":46,"value":866}," route handlers, not just queries\u002Fmutations — fails to bundle: Convex's default runtime is a V8 isolate with no Node builtins. Either move that code into an action file starting with ",{"type":41,"tag":49,"props":868,"children":870},{"className":869},[],[871],{"type":46,"value":640},{"type":46,"value":873},", or, for crypto specifically, use the ambient Web Crypto API (",{"type":41,"tag":49,"props":875,"children":877},{"className":876},[],[878],{"type":46,"value":879},"crypto.subtle",{"type":46,"value":375},{"type":41,"tag":49,"props":882,"children":884},{"className":883},[],[885],{"type":46,"value":886},"crypto.randomUUID()",{"type":46,"value":888},") which needs no import and runs in the default runtime.",{"type":41,"tag":127,"props":890,"children":891},{},[892,904,906,911],{"type":41,"tag":87,"props":893,"children":894},{},[895,897,902],{"type":46,"value":896},"Convex functions only run from the ",{"type":41,"tag":49,"props":898,"children":900},{"className":899},[],[901],{"type":46,"value":83},{"type":46,"value":903}," directory.",{"type":46,"value":905}," Never write ",{"type":41,"tag":49,"props":907,"children":909},{"className":908},[],[910],{"type":46,"value":207},{"type":46,"value":912},", queries, mutations, or actions at the project root — they silently never deploy.",{"type":41,"tag":127,"props":914,"children":915},{},[916,935,936,942,944,950,952,958,960,966,968,974,975,980],{"type":41,"tag":87,"props":917,"children":918},{},[919,925,927,933],{"type":41,"tag":49,"props":920,"children":922},{"className":921},[],[923],{"type":46,"value":924},"httpRouter",{"type":46,"value":926}," has no Express-style ",{"type":41,"tag":49,"props":928,"children":930},{"className":929},[],[931],{"type":46,"value":932},":param",{"type":46,"value":934}," routes.",{"type":46,"value":682},{"type":41,"tag":49,"props":937,"children":939},{"className":938},[],[940],{"type":46,"value":941},"http.route({ path: \"\u002Fapi\u002Fusers\u002F:userId\", ... })",{"type":46,"value":943}," only ever matches that literal string — Convex's router is exact-match or ",{"type":41,"tag":49,"props":945,"children":947},{"className":946},[],[948],{"type":46,"value":949},"pathPrefix",{"type":46,"value":951},", never a dynamic segment. Use ",{"type":41,"tag":49,"props":953,"children":955},{"className":954},[],[956],{"type":46,"value":957},"pathPrefix: \"\u002Fapi\u002Fusers\u002F\"",{"type":46,"value":959}," and parse the trailing segment yourself: ",{"type":41,"tag":49,"props":961,"children":963},{"className":962},[],[964],{"type":46,"value":965},"new URL(request.url).pathname.split(\"\u002F\").pop()",{"type":46,"value":967},". A model that writes ",{"type":41,"tag":49,"props":969,"children":971},{"className":970},[],[972],{"type":46,"value":973},":id",{"type":46,"value":264},{"type":41,"tag":49,"props":976,"children":978},{"className":977},[],[979],{"type":46,"value":932},{"type":46,"value":981}," routes ships an app where every parameterized endpoint is dead code — this is one of the most common defects in generated Convex backends.",{"type":41,"tag":127,"props":983,"children":984},{},[985,1011,1013,1019,1021,1027],{"type":41,"tag":87,"props":986,"children":987},{},[988,990,996,997,1003,1005],{"type":46,"value":989},"Every ",{"type":41,"tag":49,"props":991,"children":993},{"className":992},[],[994],{"type":46,"value":995},"http.route({...})",{"type":46,"value":682},{"type":41,"tag":49,"props":998,"children":1000},{"className":999},[],[1001],{"type":46,"value":1002},"handler:",{"type":46,"value":1004}," must be wrapped in ",{"type":41,"tag":49,"props":1006,"children":1008},{"className":1007},[],[1009],{"type":46,"value":1010},"httpAction(...)",{"type":46,"value":1012}," (imported from ",{"type":41,"tag":49,"props":1014,"children":1016},{"className":1015},[],[1017],{"type":46,"value":1018},".\u002F_generated\u002Fserver",{"type":46,"value":1020},") — a bare ",{"type":41,"tag":49,"props":1022,"children":1024},{"className":1023},[],[1025],{"type":46,"value":1026},"async (ctx, request) => {...}",{"type":46,"value":1028}," is not a valid HTTP action even though it type-checks.",{"type":41,"tag":127,"props":1030,"children":1031},{},[1032,1059,1061,1067,1069,1075,1077,1083,1085,1091],{"type":41,"tag":87,"props":1033,"children":1034},{},[1035,1037,1043,1045,1051,1052,1058],{"type":46,"value":1036},"Wrap every ",{"type":41,"tag":49,"props":1038,"children":1040},{"className":1039},[],[1041],{"type":46,"value":1042},"v.id(...)",{"type":46,"value":1044},"-cast HTTP path\u002Fquery param in a ",{"type":41,"tag":49,"props":1046,"children":1048},{"className":1047},[],[1049],{"type":46,"value":1050},"try",{"type":46,"value":264},{"type":41,"tag":49,"props":1053,"children":1055},{"className":1054},[],[1056],{"type":46,"value":1057},"catch",{"type":46,"value":230},{"type":46,"value":1060}," Casting a raw URL segment ",{"type":41,"tag":49,"props":1062,"children":1064},{"className":1063},[],[1065],{"type":46,"value":1066},"as any",{"type":46,"value":1068}," into a ",{"type":41,"tag":49,"props":1070,"children":1072},{"className":1071},[],[1073],{"type":46,"value":1074},"v.id(\"table\")",{"type":46,"value":1076}," arg (",{"type":41,"tag":49,"props":1078,"children":1080},{"className":1079},[],[1081],{"type":46,"value":1082},"ctx.runQuery(api.foo.bar, { id: rawParam as any })",{"type":46,"value":1084},") throws an uncaught ",{"type":41,"tag":49,"props":1086,"children":1088},{"className":1087},[],[1089],{"type":46,"value":1090},"ArgumentValidationError",{"type":46,"value":1092}," on any malformed or wrong-table ID, which surfaces to the caller as an opaque 500 instead of a clean 400\u002F404. Catch it and return a real error response.",{"type":41,"tag":127,"props":1094,"children":1095},{},[1096,1101,1103,1108,1109,1115],{"type":41,"tag":87,"props":1097,"children":1098},{},[1099],{"type":46,"value":1100},"A mutating HTTP route with a real-world side effect (payment, ledger post, inventory decrement, order placement) needs an idempotency key.",{"type":46,"value":1102}," A client or network retry after a dropped response re-runs the handler and double-applies the effect — accept a client-supplied ",{"type":41,"tag":49,"props":1104,"children":1106},{"className":1105},[],[1107],{"type":46,"value":626},{"type":46,"value":264},{"type":41,"tag":49,"props":1110,"children":1112},{"className":1111},[],[1113],{"type":46,"value":1114},"idempotencyKey",{"type":46,"value":1116},", check for an existing row with that key first, and short-circuit if found (return the prior result rather than repeating the write).",{"type":41,"tag":127,"props":1118,"children":1119},{},[1120,1161,1162,1168,1170,1176],{"type":41,"tag":87,"props":1121,"children":1122},{},[1123,1129,1130,1136,1137,1143,1145,1151,1153,1159],{"type":41,"tag":49,"props":1124,"children":1126},{"className":1125},[],[1127],{"type":46,"value":1128},"ctx.runQuery",{"type":46,"value":264},{"type":41,"tag":49,"props":1131,"children":1133},{"className":1132},[],[1134],{"type":46,"value":1135},"ctx.runMutation",{"type":46,"value":264},{"type":41,"tag":49,"props":1138,"children":1140},{"className":1139},[],[1141],{"type":46,"value":1142},"ctx.runAction",{"type":46,"value":1144}," take a codegen'd function reference (",{"type":41,"tag":49,"props":1146,"children":1148},{"className":1147},[],[1149],{"type":46,"value":1150},"api.foo.bar",{"type":46,"value":1152}," \u002F ",{"type":41,"tag":49,"props":1154,"children":1156},{"className":1155},[],[1157],{"type":46,"value":1158},"internal.foo.bar",{"type":46,"value":1160},"), never the raw imported function.",{"type":46,"value":682},{"type":41,"tag":49,"props":1163,"children":1165},{"className":1164},[],[1166],{"type":46,"value":1167},"import * as queries from \".\u002Fqueries\"; ctx.runQuery(queries.getX, ...)",{"type":46,"value":1169}," compiles (both shapes are structurally callable) but fails at runtime — it needs ",{"type":41,"tag":49,"props":1171,"children":1173},{"className":1172},[],[1174],{"type":46,"value":1175},"import { api } from \".\u002F_generated\u002Fapi\"; ctx.runQuery(api.queries.getX, ...)",{"type":46,"value":230},{"type":41,"tag":127,"props":1178,"children":1179},{},[1180,1205],{"type":41,"tag":87,"props":1181,"children":1182},{},[1183,1185,1190,1191,1196,1198,1203],{"type":46,"value":1184},"Never call ",{"type":41,"tag":49,"props":1186,"children":1188},{"className":1187},[],[1189],{"type":46,"value":1128},{"type":46,"value":264},{"type":41,"tag":49,"props":1192,"children":1194},{"className":1193},[],[1195],{"type":46,"value":1135},{"type":46,"value":1197}," from inside a ",{"type":41,"tag":49,"props":1199,"children":1201},{"className":1200},[],[1202],{"type":46,"value":373},{"type":46,"value":1204}," handler.",{"type":46,"value":1206}," Queries must be pure reads within the same transaction; call the other query's logic directly (extract a shared helper) or move the composition into an action.",{"type":41,"tag":127,"props":1208,"children":1209},{},[1210,1232,1233,1239,1241,1247,1249,1255,1257,1263,1264,1270],{"type":41,"tag":87,"props":1211,"children":1212},{},[1213,1218,1219,1224,1225,1230],{"type":41,"tag":49,"props":1214,"children":1216},{"className":1215},[],[1217],{"type":46,"value":1128},{"type":46,"value":264},{"type":41,"tag":49,"props":1220,"children":1222},{"className":1221},[],[1223],{"type":46,"value":1135},{"type":46,"value":264},{"type":41,"tag":49,"props":1226,"children":1228},{"className":1227},[],[1229],{"type":46,"value":1142},{"type":46,"value":1231}," never take a string.",{"type":46,"value":682},{"type":41,"tag":49,"props":1234,"children":1236},{"className":1235},[],[1237],{"type":46,"value":1238},"ctx.runMutation(\"users:getOrCreateUser\", {...})",{"type":46,"value":1240}," is not a ",{"type":41,"tag":49,"props":1242,"children":1244},{"className":1243},[],[1245],{"type":46,"value":1246},"FunctionReference",{"type":46,"value":1248}," — it type-checks as a string but fails at runtime\u002Fdeploy. Always ",{"type":41,"tag":49,"props":1250,"children":1252},{"className":1251},[],[1253],{"type":46,"value":1254},"import { api, internal } from \".\u002F_generated\u002Fapi\"",{"type":46,"value":1256}," and pass ",{"type":41,"tag":49,"props":1258,"children":1260},{"className":1259},[],[1261],{"type":46,"value":1262},"api.users.getOrCreateUser",{"type":46,"value":807},{"type":41,"tag":49,"props":1265,"children":1267},{"className":1266},[],[1268],{"type":46,"value":1269},"internal.users.getOrCreateUser",{"type":46,"value":183},{"type":41,"tag":127,"props":1272,"children":1273},{},[1274,1293,1294,1300,1301,1307,1309,1315,1316,1322,1323,1329,1330,1336,1337,1343,1345,1350,1352,1358,1360,1365,1367,1373],{"type":41,"tag":87,"props":1275,"children":1276},{},[1277,1278,1284,1285,1291],{"type":46,"value":238},{"type":41,"tag":49,"props":1279,"children":1281},{"className":1280},[],[1282],{"type":46,"value":1283},".count()",{"type":46,"value":160},{"type":41,"tag":49,"props":1286,"children":1288},{"className":1287},[],[1289],{"type":46,"value":1290},".skip()",{"type":46,"value":1292}," on a Convex query builder.",{"type":46,"value":682},{"type":41,"tag":49,"props":1295,"children":1297},{"className":1296},[],[1298],{"type":46,"value":1299},"ctx.db.query(...).withIndex(...).count()",{"type":46,"value":458},{"type":41,"tag":49,"props":1302,"children":1304},{"className":1303},[],[1305],{"type":46,"value":1306},".order(\"desc\").skip(n).take(m)",{"type":46,"value":1308}," are both hallucinated APIs — the builder only has ",{"type":41,"tag":49,"props":1310,"children":1312},{"className":1311},[],[1313],{"type":46,"value":1314},"collect",{"type":46,"value":264},{"type":41,"tag":49,"props":1317,"children":1319},{"className":1318},[],[1320],{"type":46,"value":1321},"take",{"type":46,"value":264},{"type":41,"tag":49,"props":1324,"children":1326},{"className":1325},[],[1327],{"type":46,"value":1328},"first",{"type":46,"value":264},{"type":41,"tag":49,"props":1331,"children":1333},{"className":1332},[],[1334],{"type":46,"value":1335},"unique",{"type":46,"value":264},{"type":41,"tag":49,"props":1338,"children":1340},{"className":1339},[],[1341],{"type":46,"value":1342},"paginate",{"type":46,"value":1344},". For a count, ",{"type":41,"tag":49,"props":1346,"children":1348},{"className":1347},[],[1349],{"type":46,"value":140},{"type":46,"value":1351}," and read ",{"type":41,"tag":49,"props":1353,"children":1355},{"className":1354},[],[1356],{"type":46,"value":1357},".length",{"type":46,"value":1359}," (bounded tables) or maintain a running counter field on the parent document (unbounded ones). For an offset page, use cursor-based ",{"type":41,"tag":49,"props":1361,"children":1363},{"className":1362},[],[1364],{"type":46,"value":158},{"type":46,"value":1366},", not ",{"type":41,"tag":49,"props":1368,"children":1370},{"className":1369},[],[1371],{"type":46,"value":1372},".skip(n)",{"type":46,"value":230},{"type":41,"tag":127,"props":1375,"children":1376},{},[1377,1387,1388,1394,1396,1402,1404,1410,1412,1418,1420,1425],{"type":41,"tag":87,"props":1378,"children":1379},{},[1380,1385],{"type":41,"tag":49,"props":1381,"children":1383},{"className":1382},[],[1384],{"type":46,"value":166},{"type":46,"value":1386}," then filter-in-JS silently drops correct rows, not just perf.",{"type":46,"value":682},{"type":41,"tag":49,"props":1389,"children":1391},{"className":1390},[],[1392],{"type":46,"value":1393},"ctx.db.query(...).take(1000)",{"type":46,"value":1395}," followed by an in-memory ",{"type":41,"tag":49,"props":1397,"children":1399},{"className":1398},[],[1400],{"type":46,"value":1401},".filter(...)",{"type":46,"value":1403},"\u002Fsort assumes the answer is inside the first ",{"type":41,"tag":49,"props":1405,"children":1407},{"className":1406},[],[1408],{"type":46,"value":1409},"n",{"type":46,"value":1411}," rows fetched in ",{"type":41,"tag":1413,"props":1414,"children":1415},"em",{},[1416],{"type":46,"value":1417},"index\u002Fcreation order",{"type":46,"value":1419}," — once the table exceeds ",{"type":41,"tag":49,"props":1421,"children":1423},{"className":1422},[],[1424],{"type":46,"value":1409},{"type":46,"value":1426},", matching rows beyond the cutoff are silently missing from the result (not an error, just quietly wrong: \"newest N\" becomes \"oldest N\", a dedupe check against the first N stops catching duplicates, a tag\u002Fstatus filter returns an empty page even though matches exist further down). If you need \"all rows matching X,\" query by an index on X, not by taking N unfiltered rows and filtering client-side afterward.",{"type":41,"tag":127,"props":1428,"children":1429},{},[1430,1435,1437,1443,1445,1451,1452,1458,1459,1465,1467,1473,1475,1480,1482,1487,1489,1495,1497,1502,1503,1508,1510,1516,1518,1523],{"type":41,"tag":87,"props":1431,"children":1432},{},[1433],{"type":46,"value":1434},"Every client-controlled numeric\u002Fdate-range argument needs an upper bound, not just a lower one.",{"type":46,"value":1436}," An index range built with only ",{"type":41,"tag":49,"props":1438,"children":1440},{"className":1439},[],[1441],{"type":46,"value":1442},"q.gte(...)",{"type":46,"value":1444}," (or an HTTP query-string ",{"type":41,"tag":49,"props":1446,"children":1448},{"className":1447},[],[1449],{"type":46,"value":1450},"limit",{"type":46,"value":264},{"type":41,"tag":49,"props":1453,"children":1455},{"className":1454},[],[1456],{"type":46,"value":1457},"from",{"type":46,"value":264},{"type":41,"tag":49,"props":1460,"children":1462},{"className":1461},[],[1463],{"type":46,"value":1464},"to",{"type":46,"value":1466}," parsed with a bare ",{"type":41,"tag":49,"props":1468,"children":1470},{"className":1469},[],[1471],{"type":46,"value":1472},"Number(...)",{"type":46,"value":1474},") is unbounded on the open side — a \"future reminders\" query with no ",{"type":41,"tag":49,"props":1476,"children":1478},{"className":1477},[],[1479],{"type":46,"value":291},{"type":46,"value":1481}," upper bound reads every row forever forward; an HTTP ",{"type":41,"tag":49,"props":1483,"children":1485},{"className":1484},[],[1486],{"type":46,"value":1450},{"type":46,"value":1488}," param with no ",{"type":41,"tag":49,"props":1490,"children":1492},{"className":1491},[],[1493],{"type":46,"value":1494},"Math.min",{"type":46,"value":1496},"\u002Finteger check lets ",{"type":41,"tag":49,"props":1498,"children":1500},{"className":1499},[],[1501],{"type":46,"value":565},{"type":46,"value":375},{"type":41,"tag":49,"props":1504,"children":1506},{"className":1505},[],[1507],{"type":46,"value":572},{"type":46,"value":1509},", or a negative number reach ",{"type":41,"tag":49,"props":1511,"children":1513},{"className":1512},[],[1514],{"type":46,"value":1515},".take(...)",{"type":46,"value":1517}," and crash or return nonsense. Validate both ends: reject ",{"type":41,"tag":49,"props":1519,"children":1521},{"className":1520},[],[1522],{"type":46,"value":565},{"type":46,"value":1524},"\u002Fnon-finite\u002Fnegative, clamp to a sane max, and give every index range both a floor and a ceiling.",{"type":41,"tag":111,"props":1526,"children":1528},{"id":1527},"self-verify-before-declaring-backend-work-done",[1529],{"type":46,"value":1530},"Self-verify — before declaring backend work done",{"type":41,"tag":42,"props":1532,"children":1533},{},[1534],{"type":46,"value":1535},"Before you call any backend work finished, verify it actually compiles and pushes:",{"type":41,"tag":1537,"props":1538,"children":1539},"ol",{},[1540,1552],{"type":41,"tag":127,"props":1541,"children":1542},{},[1543,1545,1551],{"type":46,"value":1544},"Run ",{"type":41,"tag":49,"props":1546,"children":1548},{"className":1547},[],[1549],{"type":46,"value":1550},"npx tsc --noEmit",{"type":46,"value":230},{"type":41,"tag":127,"props":1553,"children":1554},{},[1555,1557,1563],{"type":46,"value":1556},"When a deployment is available — or via a local anonymous one, ",{"type":41,"tag":49,"props":1558,"children":1560},{"className":1559},[],[1561],{"type":46,"value":1562},"CONVEX_AGENT_MODE=anonymous npx convex dev --once",{"type":46,"value":1564}," — push it.",{"type":41,"tag":42,"props":1566,"children":1567},{},[1568,1573],{"type":41,"tag":87,"props":1569,"children":1570},{},[1571],{"type":46,"value":1572},"Fix every error either one reports before finishing.",{"type":46,"value":1574}," One verify round catches the class of defect that otherwise breaks the deploy after you've already reported success: a wrong relative import, a duplicate symbol, an unbalanced paren. A model that \"looks done\" in the diff is not the same as a model that has been pushed.",{"type":41,"tag":111,"props":1576,"children":1578},{"id":1577},"non-negotiable-rules",[1579],{"type":46,"value":1580},"Non-negotiable rules",{"type":41,"tag":1582,"props":1583,"children":1585},"h3",{"id":1584},"function-syntax-object-form-validators-returns",[1586],{"type":46,"value":1587},"Function syntax — object form, validators, returns",{"type":41,"tag":1589,"props":1590,"children":1595},"pre",{"className":1591,"code":1592,"language":1593,"meta":1594,"style":1594},"language-ts shiki shiki-themes material-theme-lighter material-theme material-theme-palenight","import { v } from \"convex\u002Fvalues\";\nimport { query, mutation, action } from \".\u002F_generated\u002Fserver\";\n\nexport const listOpen = query({\n  args: { limit: v.optional(v.number()) },\n  returns: v.array(\n    v.object({\n      _id: v.id(\"tickets\"),\n      _creationTime: v.number(),\n      title: v.string(),\n    }),\n  ),\n  handler: async (ctx, args) => {\n    const rows = await ctx.db\n      .query(\"tickets\")\n      .withIndex(\"by_state\", (q) => q.eq(\"state\", \"open\"))\n      .order(\"desc\")\n      .take(args.limit ?? 10);\n    return rows.map((r) => ({ _id: r._id, _creationTime: r._creationTime, title: r.title }));\n  },\n});\n","ts","",[1596],{"type":41,"tag":49,"props":1597,"children":1598},{"__ignoreMap":1594},[1599,1653,1712,1722,1762,1827,1858,1884,1937,1971,2005,2022,2035,2087,2125,2159,2261,2295,2343,2483,2492],{"type":41,"tag":1600,"props":1601,"children":1604},"span",{"class":1602,"line":1603},"line",1,[1605,1610,1616,1622,1627,1632,1637,1643,1648],{"type":41,"tag":1600,"props":1606,"children":1608},{"style":1607},"--shiki-light:#39ADB5;--shiki-light-font-style:italic;--shiki-default:#89DDFF;--shiki-default-font-style:italic;--shiki-dark:#89DDFF;--shiki-dark-font-style:italic",[1609],{"type":46,"value":739},{"type":41,"tag":1600,"props":1611,"children":1613},{"style":1612},"--shiki-light:#39ADB5;--shiki-default:#89DDFF;--shiki-dark:#89DDFF",[1614],{"type":46,"value":1615}," {",{"type":41,"tag":1600,"props":1617,"children":1619},{"style":1618},"--shiki-light:#90A4AE;--shiki-default:#EEFFFF;--shiki-dark:#BABED8",[1620],{"type":46,"value":1621}," v",{"type":41,"tag":1600,"props":1623,"children":1624},{"style":1612},[1625],{"type":46,"value":1626}," }",{"type":41,"tag":1600,"props":1628,"children":1629},{"style":1607},[1630],{"type":46,"value":1631}," from",{"type":41,"tag":1600,"props":1633,"children":1634},{"style":1612},[1635],{"type":46,"value":1636}," \"",{"type":41,"tag":1600,"props":1638,"children":1640},{"style":1639},"--shiki-light:#91B859;--shiki-default:#C3E88D;--shiki-dark:#C3E88D",[1641],{"type":46,"value":1642},"convex\u002Fvalues",{"type":41,"tag":1600,"props":1644,"children":1645},{"style":1612},[1646],{"type":46,"value":1647},"\"",{"type":41,"tag":1600,"props":1649,"children":1650},{"style":1612},[1651],{"type":46,"value":1652},";\n",{"type":41,"tag":1600,"props":1654,"children":1655},{"class":1602,"line":24},[1656,1660,1664,1669,1674,1679,1683,1688,1692,1696,1700,1704,1708],{"type":41,"tag":1600,"props":1657,"children":1658},{"style":1607},[1659],{"type":46,"value":739},{"type":41,"tag":1600,"props":1661,"children":1662},{"style":1612},[1663],{"type":46,"value":1615},{"type":41,"tag":1600,"props":1665,"children":1666},{"style":1618},[1667],{"type":46,"value":1668}," query",{"type":41,"tag":1600,"props":1670,"children":1671},{"style":1612},[1672],{"type":46,"value":1673},",",{"type":41,"tag":1600,"props":1675,"children":1676},{"style":1618},[1677],{"type":46,"value":1678}," mutation",{"type":41,"tag":1600,"props":1680,"children":1681},{"style":1612},[1682],{"type":46,"value":1673},{"type":41,"tag":1600,"props":1684,"children":1685},{"style":1618},[1686],{"type":46,"value":1687}," action",{"type":41,"tag":1600,"props":1689,"children":1690},{"style":1612},[1691],{"type":46,"value":1626},{"type":41,"tag":1600,"props":1693,"children":1694},{"style":1607},[1695],{"type":46,"value":1631},{"type":41,"tag":1600,"props":1697,"children":1698},{"style":1612},[1699],{"type":46,"value":1636},{"type":41,"tag":1600,"props":1701,"children":1702},{"style":1639},[1703],{"type":46,"value":1018},{"type":41,"tag":1600,"props":1705,"children":1706},{"style":1612},[1707],{"type":46,"value":1647},{"type":41,"tag":1600,"props":1709,"children":1710},{"style":1612},[1711],{"type":46,"value":1652},{"type":41,"tag":1600,"props":1713,"children":1715},{"class":1602,"line":1714},3,[1716],{"type":41,"tag":1600,"props":1717,"children":1719},{"emptyLinePlaceholder":1718},true,[1720],{"type":46,"value":1721},"\n",{"type":41,"tag":1600,"props":1723,"children":1725},{"class":1602,"line":1724},4,[1726,1731,1737,1742,1747,1752,1757],{"type":41,"tag":1600,"props":1727,"children":1728},{"style":1607},[1729],{"type":46,"value":1730},"export",{"type":41,"tag":1600,"props":1732,"children":1734},{"style":1733},"--shiki-light:#9C3EDA;--shiki-default:#C792EA;--shiki-dark:#C792EA",[1735],{"type":46,"value":1736}," const",{"type":41,"tag":1600,"props":1738,"children":1739},{"style":1618},[1740],{"type":46,"value":1741}," listOpen ",{"type":41,"tag":1600,"props":1743,"children":1744},{"style":1612},[1745],{"type":46,"value":1746},"=",{"type":41,"tag":1600,"props":1748,"children":1750},{"style":1749},"--shiki-light:#6182B8;--shiki-default:#82AAFF;--shiki-dark:#82AAFF",[1751],{"type":46,"value":1668},{"type":41,"tag":1600,"props":1753,"children":1754},{"style":1618},[1755],{"type":46,"value":1756},"(",{"type":41,"tag":1600,"props":1758,"children":1759},{"style":1612},[1760],{"type":46,"value":1761},"{\n",{"type":41,"tag":1600,"props":1763,"children":1765},{"class":1602,"line":1764},5,[1766,1772,1777,1781,1786,1790,1794,1798,1803,1808,1812,1817,1822],{"type":41,"tag":1600,"props":1767,"children":1769},{"style":1768},"--shiki-light:#E53935;--shiki-default:#F07178;--shiki-dark:#F07178",[1770],{"type":46,"value":1771},"  args",{"type":41,"tag":1600,"props":1773,"children":1774},{"style":1612},[1775],{"type":46,"value":1776},":",{"type":41,"tag":1600,"props":1778,"children":1779},{"style":1612},[1780],{"type":46,"value":1615},{"type":41,"tag":1600,"props":1782,"children":1783},{"style":1768},[1784],{"type":46,"value":1785}," limit",{"type":41,"tag":1600,"props":1787,"children":1788},{"style":1612},[1789],{"type":46,"value":1776},{"type":41,"tag":1600,"props":1791,"children":1792},{"style":1618},[1793],{"type":46,"value":1621},{"type":41,"tag":1600,"props":1795,"children":1796},{"style":1612},[1797],{"type":46,"value":230},{"type":41,"tag":1600,"props":1799,"children":1800},{"style":1749},[1801],{"type":46,"value":1802},"optional",{"type":41,"tag":1600,"props":1804,"children":1805},{"style":1618},[1806],{"type":46,"value":1807},"(v",{"type":41,"tag":1600,"props":1809,"children":1810},{"style":1612},[1811],{"type":46,"value":230},{"type":41,"tag":1600,"props":1813,"children":1814},{"style":1749},[1815],{"type":46,"value":1816},"number",{"type":41,"tag":1600,"props":1818,"children":1819},{"style":1618},[1820],{"type":46,"value":1821},"()) ",{"type":41,"tag":1600,"props":1823,"children":1824},{"style":1612},[1825],{"type":46,"value":1826},"},\n",{"type":41,"tag":1600,"props":1828,"children":1830},{"class":1602,"line":1829},6,[1831,1836,1840,1844,1848,1853],{"type":41,"tag":1600,"props":1832,"children":1833},{"style":1768},[1834],{"type":46,"value":1835},"  returns",{"type":41,"tag":1600,"props":1837,"children":1838},{"style":1612},[1839],{"type":46,"value":1776},{"type":41,"tag":1600,"props":1841,"children":1842},{"style":1618},[1843],{"type":46,"value":1621},{"type":41,"tag":1600,"props":1845,"children":1846},{"style":1612},[1847],{"type":46,"value":230},{"type":41,"tag":1600,"props":1849,"children":1850},{"style":1749},[1851],{"type":46,"value":1852},"array",{"type":41,"tag":1600,"props":1854,"children":1855},{"style":1618},[1856],{"type":46,"value":1857},"(\n",{"type":41,"tag":1600,"props":1859,"children":1861},{"class":1602,"line":1860},7,[1862,1867,1871,1876,1880],{"type":41,"tag":1600,"props":1863,"children":1864},{"style":1618},[1865],{"type":46,"value":1866},"    v",{"type":41,"tag":1600,"props":1868,"children":1869},{"style":1612},[1870],{"type":46,"value":230},{"type":41,"tag":1600,"props":1872,"children":1873},{"style":1749},[1874],{"type":46,"value":1875},"object",{"type":41,"tag":1600,"props":1877,"children":1878},{"style":1618},[1879],{"type":46,"value":1756},{"type":41,"tag":1600,"props":1881,"children":1882},{"style":1612},[1883],{"type":46,"value":1761},{"type":41,"tag":1600,"props":1885,"children":1887},{"class":1602,"line":1886},8,[1888,1893,1897,1901,1905,1910,1914,1918,1923,1927,1932],{"type":41,"tag":1600,"props":1889,"children":1890},{"style":1768},[1891],{"type":46,"value":1892},"      _id",{"type":41,"tag":1600,"props":1894,"children":1895},{"style":1612},[1896],{"type":46,"value":1776},{"type":41,"tag":1600,"props":1898,"children":1899},{"style":1618},[1900],{"type":46,"value":1621},{"type":41,"tag":1600,"props":1902,"children":1903},{"style":1612},[1904],{"type":46,"value":230},{"type":41,"tag":1600,"props":1906,"children":1907},{"style":1749},[1908],{"type":46,"value":1909},"id",{"type":41,"tag":1600,"props":1911,"children":1912},{"style":1618},[1913],{"type":46,"value":1756},{"type":41,"tag":1600,"props":1915,"children":1916},{"style":1612},[1917],{"type":46,"value":1647},{"type":41,"tag":1600,"props":1919,"children":1920},{"style":1639},[1921],{"type":46,"value":1922},"tickets",{"type":41,"tag":1600,"props":1924,"children":1925},{"style":1612},[1926],{"type":46,"value":1647},{"type":41,"tag":1600,"props":1928,"children":1929},{"style":1618},[1930],{"type":46,"value":1931},")",{"type":41,"tag":1600,"props":1933,"children":1934},{"style":1612},[1935],{"type":46,"value":1936},",\n",{"type":41,"tag":1600,"props":1938,"children":1940},{"class":1602,"line":1939},9,[1941,1946,1950,1954,1958,1962,1967],{"type":41,"tag":1600,"props":1942,"children":1943},{"style":1768},[1944],{"type":46,"value":1945},"      _creationTime",{"type":41,"tag":1600,"props":1947,"children":1948},{"style":1612},[1949],{"type":46,"value":1776},{"type":41,"tag":1600,"props":1951,"children":1952},{"style":1618},[1953],{"type":46,"value":1621},{"type":41,"tag":1600,"props":1955,"children":1956},{"style":1612},[1957],{"type":46,"value":230},{"type":41,"tag":1600,"props":1959,"children":1960},{"style":1749},[1961],{"type":46,"value":1816},{"type":41,"tag":1600,"props":1963,"children":1964},{"style":1618},[1965],{"type":46,"value":1966},"()",{"type":41,"tag":1600,"props":1968,"children":1969},{"style":1612},[1970],{"type":46,"value":1936},{"type":41,"tag":1600,"props":1972,"children":1974},{"class":1602,"line":1973},10,[1975,1980,1984,1988,1992,1997,2001],{"type":41,"tag":1600,"props":1976,"children":1977},{"style":1768},[1978],{"type":46,"value":1979},"      title",{"type":41,"tag":1600,"props":1981,"children":1982},{"style":1612},[1983],{"type":46,"value":1776},{"type":41,"tag":1600,"props":1985,"children":1986},{"style":1618},[1987],{"type":46,"value":1621},{"type":41,"tag":1600,"props":1989,"children":1990},{"style":1612},[1991],{"type":46,"value":230},{"type":41,"tag":1600,"props":1993,"children":1994},{"style":1749},[1995],{"type":46,"value":1996},"string",{"type":41,"tag":1600,"props":1998,"children":1999},{"style":1618},[2000],{"type":46,"value":1966},{"type":41,"tag":1600,"props":2002,"children":2003},{"style":1612},[2004],{"type":46,"value":1936},{"type":41,"tag":1600,"props":2006,"children":2008},{"class":1602,"line":2007},11,[2009,2014,2018],{"type":41,"tag":1600,"props":2010,"children":2011},{"style":1612},[2012],{"type":46,"value":2013},"    }",{"type":41,"tag":1600,"props":2015,"children":2016},{"style":1618},[2017],{"type":46,"value":1931},{"type":41,"tag":1600,"props":2019,"children":2020},{"style":1612},[2021],{"type":46,"value":1936},{"type":41,"tag":1600,"props":2023,"children":2025},{"class":1602,"line":2024},12,[2026,2031],{"type":41,"tag":1600,"props":2027,"children":2028},{"style":1618},[2029],{"type":46,"value":2030},"  )",{"type":41,"tag":1600,"props":2032,"children":2033},{"style":1612},[2034],{"type":46,"value":1936},{"type":41,"tag":1600,"props":2036,"children":2038},{"class":1602,"line":2037},13,[2039,2044,2048,2053,2058,2064,2068,2073,2077,2082],{"type":41,"tag":1600,"props":2040,"children":2041},{"style":1749},[2042],{"type":46,"value":2043},"  handler",{"type":41,"tag":1600,"props":2045,"children":2046},{"style":1612},[2047],{"type":46,"value":1776},{"type":41,"tag":1600,"props":2049,"children":2050},{"style":1733},[2051],{"type":46,"value":2052}," async",{"type":41,"tag":1600,"props":2054,"children":2055},{"style":1612},[2056],{"type":46,"value":2057}," (",{"type":41,"tag":1600,"props":2059,"children":2061},{"style":2060},"--shiki-light:#90A4AE;--shiki-light-font-style:italic;--shiki-default:#EEFFFF;--shiki-default-font-style:italic;--shiki-dark:#BABED8;--shiki-dark-font-style:italic",[2062],{"type":46,"value":2063},"ctx",{"type":41,"tag":1600,"props":2065,"children":2066},{"style":1612},[2067],{"type":46,"value":1673},{"type":41,"tag":1600,"props":2069,"children":2070},{"style":2060},[2071],{"type":46,"value":2072}," args",{"type":41,"tag":1600,"props":2074,"children":2075},{"style":1612},[2076],{"type":46,"value":1931},{"type":41,"tag":1600,"props":2078,"children":2079},{"style":1733},[2080],{"type":46,"value":2081}," =>",{"type":41,"tag":1600,"props":2083,"children":2084},{"style":1612},[2085],{"type":46,"value":2086}," {\n",{"type":41,"tag":1600,"props":2088,"children":2090},{"class":1602,"line":2089},14,[2091,2096,2101,2106,2111,2116,2120],{"type":41,"tag":1600,"props":2092,"children":2093},{"style":1733},[2094],{"type":46,"value":2095},"    const",{"type":41,"tag":1600,"props":2097,"children":2098},{"style":1618},[2099],{"type":46,"value":2100}," rows",{"type":41,"tag":1600,"props":2102,"children":2103},{"style":1612},[2104],{"type":46,"value":2105}," =",{"type":41,"tag":1600,"props":2107,"children":2108},{"style":1607},[2109],{"type":46,"value":2110}," await",{"type":41,"tag":1600,"props":2112,"children":2113},{"style":1618},[2114],{"type":46,"value":2115}," ctx",{"type":41,"tag":1600,"props":2117,"children":2118},{"style":1612},[2119],{"type":46,"value":230},{"type":41,"tag":1600,"props":2121,"children":2122},{"style":1618},[2123],{"type":46,"value":2124},"db\n",{"type":41,"tag":1600,"props":2126,"children":2128},{"class":1602,"line":2127},15,[2129,2134,2138,2142,2146,2150,2154],{"type":41,"tag":1600,"props":2130,"children":2131},{"style":1612},[2132],{"type":46,"value":2133},"      .",{"type":41,"tag":1600,"props":2135,"children":2136},{"style":1749},[2137],{"type":46,"value":373},{"type":41,"tag":1600,"props":2139,"children":2140},{"style":1768},[2141],{"type":46,"value":1756},{"type":41,"tag":1600,"props":2143,"children":2144},{"style":1612},[2145],{"type":46,"value":1647},{"type":41,"tag":1600,"props":2147,"children":2148},{"style":1639},[2149],{"type":46,"value":1922},{"type":41,"tag":1600,"props":2151,"children":2152},{"style":1612},[2153],{"type":46,"value":1647},{"type":41,"tag":1600,"props":2155,"children":2156},{"style":1768},[2157],{"type":46,"value":2158},")\n",{"type":41,"tag":1600,"props":2160,"children":2162},{"class":1602,"line":2161},16,[2163,2167,2171,2175,2179,2184,2188,2192,2196,2201,2205,2209,2214,2218,2222,2226,2230,2235,2239,2243,2247,2252,2256],{"type":41,"tag":1600,"props":2164,"children":2165},{"style":1612},[2166],{"type":46,"value":2133},{"type":41,"tag":1600,"props":2168,"children":2169},{"style":1749},[2170],{"type":46,"value":252},{"type":41,"tag":1600,"props":2172,"children":2173},{"style":1768},[2174],{"type":46,"value":1756},{"type":41,"tag":1600,"props":2176,"children":2177},{"style":1612},[2178],{"type":46,"value":1647},{"type":41,"tag":1600,"props":2180,"children":2181},{"style":1639},[2182],{"type":46,"value":2183},"by_state",{"type":41,"tag":1600,"props":2185,"children":2186},{"style":1612},[2187],{"type":46,"value":1647},{"type":41,"tag":1600,"props":2189,"children":2190},{"style":1612},[2191],{"type":46,"value":1673},{"type":41,"tag":1600,"props":2193,"children":2194},{"style":1612},[2195],{"type":46,"value":2057},{"type":41,"tag":1600,"props":2197,"children":2198},{"style":2060},[2199],{"type":46,"value":2200},"q",{"type":41,"tag":1600,"props":2202,"children":2203},{"style":1612},[2204],{"type":46,"value":1931},{"type":41,"tag":1600,"props":2206,"children":2207},{"style":1733},[2208],{"type":46,"value":2081},{"type":41,"tag":1600,"props":2210,"children":2211},{"style":1618},[2212],{"type":46,"value":2213}," q",{"type":41,"tag":1600,"props":2215,"children":2216},{"style":1612},[2217],{"type":46,"value":230},{"type":41,"tag":1600,"props":2219,"children":2220},{"style":1749},[2221],{"type":46,"value":262},{"type":41,"tag":1600,"props":2223,"children":2224},{"style":1768},[2225],{"type":46,"value":1756},{"type":41,"tag":1600,"props":2227,"children":2228},{"style":1612},[2229],{"type":46,"value":1647},{"type":41,"tag":1600,"props":2231,"children":2232},{"style":1639},[2233],{"type":46,"value":2234},"state",{"type":41,"tag":1600,"props":2236,"children":2237},{"style":1612},[2238],{"type":46,"value":1647},{"type":41,"tag":1600,"props":2240,"children":2241},{"style":1612},[2242],{"type":46,"value":1673},{"type":41,"tag":1600,"props":2244,"children":2245},{"style":1612},[2246],{"type":46,"value":1636},{"type":41,"tag":1600,"props":2248,"children":2249},{"style":1639},[2250],{"type":46,"value":2251},"open",{"type":41,"tag":1600,"props":2253,"children":2254},{"style":1612},[2255],{"type":46,"value":1647},{"type":41,"tag":1600,"props":2257,"children":2258},{"style":1768},[2259],{"type":46,"value":2260},"))\n",{"type":41,"tag":1600,"props":2262,"children":2264},{"class":1602,"line":2263},17,[2265,2269,2274,2278,2282,2287,2291],{"type":41,"tag":1600,"props":2266,"children":2267},{"style":1612},[2268],{"type":46,"value":2133},{"type":41,"tag":1600,"props":2270,"children":2271},{"style":1749},[2272],{"type":46,"value":2273},"order",{"type":41,"tag":1600,"props":2275,"children":2276},{"style":1768},[2277],{"type":46,"value":1756},{"type":41,"tag":1600,"props":2279,"children":2280},{"style":1612},[2281],{"type":46,"value":1647},{"type":41,"tag":1600,"props":2283,"children":2284},{"style":1639},[2285],{"type":46,"value":2286},"desc",{"type":41,"tag":1600,"props":2288,"children":2289},{"style":1612},[2290],{"type":46,"value":1647},{"type":41,"tag":1600,"props":2292,"children":2293},{"style":1768},[2294],{"type":46,"value":2158},{"type":41,"tag":1600,"props":2296,"children":2298},{"class":1602,"line":2297},18,[2299,2303,2307,2311,2316,2320,2324,2329,2335,2339],{"type":41,"tag":1600,"props":2300,"children":2301},{"style":1612},[2302],{"type":46,"value":2133},{"type":41,"tag":1600,"props":2304,"children":2305},{"style":1749},[2306],{"type":46,"value":1321},{"type":41,"tag":1600,"props":2308,"children":2309},{"style":1768},[2310],{"type":46,"value":1756},{"type":41,"tag":1600,"props":2312,"children":2313},{"style":1618},[2314],{"type":46,"value":2315},"args",{"type":41,"tag":1600,"props":2317,"children":2318},{"style":1612},[2319],{"type":46,"value":230},{"type":41,"tag":1600,"props":2321,"children":2322},{"style":1618},[2323],{"type":46,"value":1450},{"type":41,"tag":1600,"props":2325,"children":2326},{"style":1612},[2327],{"type":46,"value":2328}," ??",{"type":41,"tag":1600,"props":2330,"children":2332},{"style":2331},"--shiki-light:#F76D47;--shiki-default:#F78C6C;--shiki-dark:#F78C6C",[2333],{"type":46,"value":2334}," 10",{"type":41,"tag":1600,"props":2336,"children":2337},{"style":1768},[2338],{"type":46,"value":1931},{"type":41,"tag":1600,"props":2340,"children":2341},{"style":1612},[2342],{"type":46,"value":1652},{"type":41,"tag":1600,"props":2344,"children":2346},{"class":1602,"line":2345},19,[2347,2352,2356,2360,2365,2369,2373,2378,2382,2386,2390,2395,2400,2404,2409,2413,2418,2422,2427,2431,2435,2439,2444,2448,2453,2457,2461,2465,2470,2474,2479],{"type":41,"tag":1600,"props":2348,"children":2349},{"style":1607},[2350],{"type":46,"value":2351},"    return",{"type":41,"tag":1600,"props":2353,"children":2354},{"style":1618},[2355],{"type":46,"value":2100},{"type":41,"tag":1600,"props":2357,"children":2358},{"style":1612},[2359],{"type":46,"value":230},{"type":41,"tag":1600,"props":2361,"children":2362},{"style":1749},[2363],{"type":46,"value":2364},"map",{"type":41,"tag":1600,"props":2366,"children":2367},{"style":1768},[2368],{"type":46,"value":1756},{"type":41,"tag":1600,"props":2370,"children":2371},{"style":1612},[2372],{"type":46,"value":1756},{"type":41,"tag":1600,"props":2374,"children":2375},{"style":2060},[2376],{"type":46,"value":2377},"r",{"type":41,"tag":1600,"props":2379,"children":2380},{"style":1612},[2381],{"type":46,"value":1931},{"type":41,"tag":1600,"props":2383,"children":2384},{"style":1733},[2385],{"type":46,"value":2081},{"type":41,"tag":1600,"props":2387,"children":2388},{"style":1768},[2389],{"type":46,"value":2057},{"type":41,"tag":1600,"props":2391,"children":2392},{"style":1612},[2393],{"type":46,"value":2394},"{",{"type":41,"tag":1600,"props":2396,"children":2397},{"style":1768},[2398],{"type":46,"value":2399}," _id",{"type":41,"tag":1600,"props":2401,"children":2402},{"style":1612},[2403],{"type":46,"value":1776},{"type":41,"tag":1600,"props":2405,"children":2406},{"style":1618},[2407],{"type":46,"value":2408}," r",{"type":41,"tag":1600,"props":2410,"children":2411},{"style":1612},[2412],{"type":46,"value":230},{"type":41,"tag":1600,"props":2414,"children":2415},{"style":1618},[2416],{"type":46,"value":2417},"_id",{"type":41,"tag":1600,"props":2419,"children":2420},{"style":1612},[2421],{"type":46,"value":1673},{"type":41,"tag":1600,"props":2423,"children":2424},{"style":1768},[2425],{"type":46,"value":2426}," _creationTime",{"type":41,"tag":1600,"props":2428,"children":2429},{"style":1612},[2430],{"type":46,"value":1776},{"type":41,"tag":1600,"props":2432,"children":2433},{"style":1618},[2434],{"type":46,"value":2408},{"type":41,"tag":1600,"props":2436,"children":2437},{"style":1612},[2438],{"type":46,"value":230},{"type":41,"tag":1600,"props":2440,"children":2441},{"style":1618},[2442],{"type":46,"value":2443},"_creationTime",{"type":41,"tag":1600,"props":2445,"children":2446},{"style":1612},[2447],{"type":46,"value":1673},{"type":41,"tag":1600,"props":2449,"children":2450},{"style":1768},[2451],{"type":46,"value":2452}," title",{"type":41,"tag":1600,"props":2454,"children":2455},{"style":1612},[2456],{"type":46,"value":1776},{"type":41,"tag":1600,"props":2458,"children":2459},{"style":1618},[2460],{"type":46,"value":2408},{"type":41,"tag":1600,"props":2462,"children":2463},{"style":1612},[2464],{"type":46,"value":230},{"type":41,"tag":1600,"props":2466,"children":2467},{"style":1618},[2468],{"type":46,"value":2469},"title",{"type":41,"tag":1600,"props":2471,"children":2472},{"style":1612},[2473],{"type":46,"value":1626},{"type":41,"tag":1600,"props":2475,"children":2476},{"style":1768},[2477],{"type":46,"value":2478},"))",{"type":41,"tag":1600,"props":2480,"children":2481},{"style":1612},[2482],{"type":46,"value":1652},{"type":41,"tag":1600,"props":2484,"children":2486},{"class":1602,"line":2485},20,[2487],{"type":41,"tag":1600,"props":2488,"children":2489},{"style":1612},[2490],{"type":46,"value":2491},"  },\n",{"type":41,"tag":1600,"props":2493,"children":2495},{"class":1602,"line":2494},21,[2496,2501,2505],{"type":41,"tag":1600,"props":2497,"children":2498},{"style":1612},[2499],{"type":46,"value":2500},"}",{"type":41,"tag":1600,"props":2502,"children":2503},{"style":1618},[2504],{"type":46,"value":1931},{"type":41,"tag":1600,"props":2506,"children":2507},{"style":1612},[2508],{"type":46,"value":1652},{"type":41,"tag":123,"props":2510,"children":2511},{},[2512,2529,2551,2571],{"type":41,"tag":127,"props":2513,"children":2514},{},[2515,2520,2522,2528],{"type":41,"tag":87,"props":2516,"children":2517},{},[2518],{"type":46,"value":2519},"Object form only.",{"type":46,"value":2521}," Never the legacy positional ",{"type":41,"tag":49,"props":2523,"children":2525},{"className":2524},[],[2526],{"type":46,"value":2527},"query(args, handler)",{"type":46,"value":230},{"type":41,"tag":127,"props":2530,"children":2531},{},[2532,2549],{"type":41,"tag":87,"props":2533,"children":2534},{},[2535,2540,2541,2547],{"type":41,"tag":49,"props":2536,"children":2538},{"className":2537},[],[2539],{"type":46,"value":2315},{"type":46,"value":458},{"type":41,"tag":49,"props":2542,"children":2544},{"className":2543},[],[2545],{"type":46,"value":2546},"returns",{"type":46,"value":2548}," validators on every registered function",{"type":46,"value":2550},", internal or public. No exceptions. They are runtime guards, not type hints.",{"type":41,"tag":127,"props":2552,"children":2553},{},[2554,2563,2565,2570],{"type":41,"tag":87,"props":2555,"children":2556},{},[2557],{"type":41,"tag":49,"props":2558,"children":2560},{"className":2559},[],[2561],{"type":46,"value":2562},"v.id(tableName)",{"type":46,"value":2564}," for IDs, never ",{"type":41,"tag":49,"props":2566,"children":2568},{"className":2567},[],[2569],{"type":46,"value":502},{"type":46,"value":230},{"type":41,"tag":127,"props":2572,"children":2573},{},[2574,2585,2586,2592,2594,2600],{"type":41,"tag":87,"props":2575,"children":2576},{},[2577,2583],{"type":41,"tag":49,"props":2578,"children":2580},{"className":2579},[],[2581],{"type":46,"value":2582},"undefined",{"type":46,"value":2584}," is not a Convex value.",{"type":46,"value":144},{"type":41,"tag":49,"props":2587,"children":2589},{"className":2588},[],[2590],{"type":46,"value":2591},"null",{"type":46,"value":2593},". Optional fields use ",{"type":41,"tag":49,"props":2595,"children":2597},{"className":2596},[],[2598],{"type":46,"value":2599},"v.optional(...)",{"type":46,"value":230},{"type":41,"tag":1582,"props":2602,"children":2604},{"id":2603},"internal-vs-public",[2605],{"type":46,"value":2606},"Internal vs public",{"type":41,"tag":123,"props":2608,"children":2609},{},[2610,2634,2657],{"type":41,"tag":127,"props":2611,"children":2612},{},[2613,2615,2620,2621,2626,2627,2632],{"type":46,"value":2614},"Public ",{"type":41,"tag":49,"props":2616,"children":2618},{"className":2617},[],[2619],{"type":46,"value":373},{"type":46,"value":1152},{"type":41,"tag":49,"props":2622,"children":2624},{"className":2623},[],[2625],{"type":46,"value":381},{"type":46,"value":1152},{"type":41,"tag":49,"props":2628,"children":2630},{"className":2629},[],[2631],{"type":46,"value":388},{"type":46,"value":2633}," = anything the client calls directly. Public surface is a liability.",{"type":41,"tag":127,"props":2635,"children":2636},{},[2637,2639,2644,2645,2650,2651,2656],{"type":46,"value":2638},"Helpers, scheduled callbacks, internal business logic = ",{"type":41,"tag":49,"props":2640,"children":2642},{"className":2641},[],[2643],{"type":46,"value":395},{"type":46,"value":1152},{"type":41,"tag":49,"props":2646,"children":2648},{"className":2647},[],[2649],{"type":46,"value":402},{"type":46,"value":1152},{"type":41,"tag":49,"props":2652,"children":2654},{"className":2653},[],[2655],{"type":46,"value":409},{"type":46,"value":230},{"type":41,"tag":127,"props":2658,"children":2659},{},[2660,2662,2668,2669,2675,2676,2682],{"type":46,"value":2661},"Default to internal. Promote to public only when a ",{"type":41,"tag":49,"props":2663,"children":2665},{"className":2664},[],[2666],{"type":46,"value":2667},"useQuery",{"type":46,"value":1152},{"type":41,"tag":49,"props":2670,"children":2672},{"className":2671},[],[2673],{"type":46,"value":2674},"useMutation",{"type":46,"value":1152},{"type":41,"tag":49,"props":2677,"children":2679},{"className":2678},[],[2680],{"type":46,"value":2681},"useAction",{"type":46,"value":2683}," on the client needs it.",{"type":41,"tag":1582,"props":2685,"children":2687},{"id":2686},"indexes-name-after-the-columns-in-order",[2688],{"type":46,"value":2689},"Indexes — name after the columns, in order",{"type":41,"tag":1589,"props":2691,"children":2693},{"className":1591,"code":2692,"language":1593,"meta":1594,"style":1594},"defineTable({ author: v.string(), channel: v.string(), text: v.string() })\n  .index(\"by_author_and_channel\", [\"author\", \"channel\"]);\n",[2694],{"type":41,"tag":49,"props":2695,"children":2696},{"__ignoreMap":1594},[2697,2805],{"type":41,"tag":1600,"props":2698,"children":2699},{"class":1602,"line":1603},[2700,2705,2709,2713,2718,2722,2726,2730,2734,2738,2742,2747,2751,2755,2759,2763,2767,2771,2776,2780,2784,2788,2792,2797,2801],{"type":41,"tag":1600,"props":2701,"children":2702},{"style":1749},[2703],{"type":46,"value":2704},"defineTable",{"type":41,"tag":1600,"props":2706,"children":2707},{"style":1618},[2708],{"type":46,"value":1756},{"type":41,"tag":1600,"props":2710,"children":2711},{"style":1612},[2712],{"type":46,"value":2394},{"type":41,"tag":1600,"props":2714,"children":2715},{"style":1768},[2716],{"type":46,"value":2717}," author",{"type":41,"tag":1600,"props":2719,"children":2720},{"style":1612},[2721],{"type":46,"value":1776},{"type":41,"tag":1600,"props":2723,"children":2724},{"style":1618},[2725],{"type":46,"value":1621},{"type":41,"tag":1600,"props":2727,"children":2728},{"style":1612},[2729],{"type":46,"value":230},{"type":41,"tag":1600,"props":2731,"children":2732},{"style":1749},[2733],{"type":46,"value":1996},{"type":41,"tag":1600,"props":2735,"children":2736},{"style":1618},[2737],{"type":46,"value":1966},{"type":41,"tag":1600,"props":2739,"children":2740},{"style":1612},[2741],{"type":46,"value":1673},{"type":41,"tag":1600,"props":2743,"children":2744},{"style":1768},[2745],{"type":46,"value":2746}," channel",{"type":41,"tag":1600,"props":2748,"children":2749},{"style":1612},[2750],{"type":46,"value":1776},{"type":41,"tag":1600,"props":2752,"children":2753},{"style":1618},[2754],{"type":46,"value":1621},{"type":41,"tag":1600,"props":2756,"children":2757},{"style":1612},[2758],{"type":46,"value":230},{"type":41,"tag":1600,"props":2760,"children":2761},{"style":1749},[2762],{"type":46,"value":1996},{"type":41,"tag":1600,"props":2764,"children":2765},{"style":1618},[2766],{"type":46,"value":1966},{"type":41,"tag":1600,"props":2768,"children":2769},{"style":1612},[2770],{"type":46,"value":1673},{"type":41,"tag":1600,"props":2772,"children":2773},{"style":1768},[2774],{"type":46,"value":2775}," text",{"type":41,"tag":1600,"props":2777,"children":2778},{"style":1612},[2779],{"type":46,"value":1776},{"type":41,"tag":1600,"props":2781,"children":2782},{"style":1618},[2783],{"type":46,"value":1621},{"type":41,"tag":1600,"props":2785,"children":2786},{"style":1612},[2787],{"type":46,"value":230},{"type":41,"tag":1600,"props":2789,"children":2790},{"style":1749},[2791],{"type":46,"value":1996},{"type":41,"tag":1600,"props":2793,"children":2794},{"style":1618},[2795],{"type":46,"value":2796},"() ",{"type":41,"tag":1600,"props":2798,"children":2799},{"style":1612},[2800],{"type":46,"value":2500},{"type":41,"tag":1600,"props":2802,"children":2803},{"style":1618},[2804],{"type":46,"value":2158},{"type":41,"tag":1600,"props":2806,"children":2807},{"class":1602,"line":24},[2808,2813,2818,2822,2826,2831,2835,2839,2844,2848,2853,2857,2861,2865,2870,2874,2879],{"type":41,"tag":1600,"props":2809,"children":2810},{"style":1612},[2811],{"type":46,"value":2812},"  .",{"type":41,"tag":1600,"props":2814,"children":2815},{"style":1749},[2816],{"type":46,"value":2817},"index",{"type":41,"tag":1600,"props":2819,"children":2820},{"style":1618},[2821],{"type":46,"value":1756},{"type":41,"tag":1600,"props":2823,"children":2824},{"style":1612},[2825],{"type":46,"value":1647},{"type":41,"tag":1600,"props":2827,"children":2828},{"style":1639},[2829],{"type":46,"value":2830},"by_author_and_channel",{"type":41,"tag":1600,"props":2832,"children":2833},{"style":1612},[2834],{"type":46,"value":1647},{"type":41,"tag":1600,"props":2836,"children":2837},{"style":1612},[2838],{"type":46,"value":1673},{"type":41,"tag":1600,"props":2840,"children":2841},{"style":1618},[2842],{"type":46,"value":2843}," [",{"type":41,"tag":1600,"props":2845,"children":2846},{"style":1612},[2847],{"type":46,"value":1647},{"type":41,"tag":1600,"props":2849,"children":2850},{"style":1639},[2851],{"type":46,"value":2852},"author",{"type":41,"tag":1600,"props":2854,"children":2855},{"style":1612},[2856],{"type":46,"value":1647},{"type":41,"tag":1600,"props":2858,"children":2859},{"style":1612},[2860],{"type":46,"value":1673},{"type":41,"tag":1600,"props":2862,"children":2863},{"style":1612},[2864],{"type":46,"value":1636},{"type":41,"tag":1600,"props":2866,"children":2867},{"style":1639},[2868],{"type":46,"value":2869},"channel",{"type":41,"tag":1600,"props":2871,"children":2872},{"style":1612},[2873],{"type":46,"value":1647},{"type":41,"tag":1600,"props":2875,"children":2876},{"style":1618},[2877],{"type":46,"value":2878},"])",{"type":41,"tag":1600,"props":2880,"children":2881},{"style":1612},[2882],{"type":46,"value":1652},{"type":41,"tag":123,"props":2884,"children":2885},{},[2886,2917,2936,2968],{"type":41,"tag":127,"props":2887,"children":2888},{},[2889,2894,2896,2901,2903,2908,2910,2916],{"type":41,"tag":87,"props":2890,"children":2891},{},[2892],{"type":46,"value":2893},"Add an index for every read path.",{"type":46,"value":2895}," Never ",{"type":41,"tag":49,"props":2897,"children":2899},{"className":2898},[],[2900],{"type":46,"value":54},{"type":46,"value":2902}," for anything you'd put in a SQL ",{"type":41,"tag":49,"props":2904,"children":2906},{"className":2905},[],[2907],{"type":46,"value":228},{"type":46,"value":2909},". Use ",{"type":41,"tag":49,"props":2911,"children":2913},{"className":2912},[],[2914],{"type":46,"value":2915},"withIndex(...)",{"type":46,"value":230},{"type":41,"tag":127,"props":2918,"children":2919},{},[2920,2922,2927,2929,2935],{"type":46,"value":2921},"Name indexes after the columns in order: ",{"type":41,"tag":49,"props":2923,"children":2925},{"className":2924},[],[2926],{"type":46,"value":2830},{"type":46,"value":2928}," for ",{"type":41,"tag":49,"props":2930,"children":2932},{"className":2931},[],[2933],{"type":46,"value":2934},"[\"author\", \"channel\"]",{"type":46,"value":230},{"type":41,"tag":127,"props":2937,"children":2938},{},[2939,2951,2953,2959,2961,2967],{"type":41,"tag":87,"props":2940,"children":2941},{},[2942,2944,2949],{"type":46,"value":2943},"Never include ",{"type":41,"tag":49,"props":2945,"children":2947},{"className":2946},[],[2948],{"type":46,"value":2443},{"type":46,"value":2950}," as a column in a custom index.",{"type":46,"value":2952}," Convex appends it automatically. Writing ",{"type":41,"tag":49,"props":2954,"children":2956},{"className":2955},[],[2957],{"type":46,"value":2958},"[\"author\", \"_creationTime\"]",{"type":46,"value":2960}," errors at push as ",{"type":41,"tag":49,"props":2962,"children":2964},{"className":2963},[],[2965],{"type":46,"value":2966},"IndexNameReserved",{"type":46,"value":230},{"type":41,"tag":127,"props":2969,"children":2970},{},[2971,2984,2985,2991,2992,2998,3000,3006],{"type":41,"tag":87,"props":2972,"children":2973},{},[2974,2976,2982],{"type":46,"value":2975},"Table names can't start with ",{"type":41,"tag":49,"props":2977,"children":2979},{"className":2978},[],[2980],{"type":46,"value":2981},"_",{"type":46,"value":2983}," either.",{"type":46,"value":682},{"type":41,"tag":49,"props":2986,"children":2988},{"className":2987},[],[2989],{"type":46,"value":2990},"_migrations: defineTable(...)",{"type":46,"value":2960},{"type":41,"tag":49,"props":2993,"children":2995},{"className":2994},[],[2996],{"type":46,"value":2997},"TableNameReserved",{"type":46,"value":2999}," — same underscore-prefix rule as index names, just one level up. Drop the leading underscore (",{"type":41,"tag":49,"props":3001,"children":3003},{"className":3002},[],[3004],{"type":46,"value":3005},"migrations: defineTable(...)",{"type":46,"value":183},{"type":41,"tag":1582,"props":3008,"children":3010},{"id":3009},"schema-evolution",[3011],{"type":46,"value":3012},"Schema evolution",{"type":41,"tag":123,"props":3014,"children":3015},{},[3016,3039,3044,3091,3104],{"type":41,"tag":127,"props":3017,"children":3018},{},[3019,3029,3031,3037],{"type":41,"tag":87,"props":3020,"children":3021},{},[3022,3024],{"type":46,"value":3023},"Add new fields as ",{"type":41,"tag":49,"props":3025,"children":3027},{"className":3026},[],[3028],{"type":46,"value":2599},{"type":46,"value":3030}," when the table has data. Required fields on existing rows = ",{"type":41,"tag":49,"props":3032,"children":3034},{"className":3033},[],[3035],{"type":46,"value":3036},"Schema validation failed",{"type":46,"value":3038}," on push.",{"type":41,"tag":127,"props":3040,"children":3041},{},[3042],{"type":46,"value":3043},"Once backfilled, tighten back to required (re-push; Convex re-validates).",{"type":41,"tag":127,"props":3045,"children":3046},{},[3047,3052,3054,3059,3061,3066,3068,3074,3076,3081,3083,3089],{"type":41,"tag":87,"props":3048,"children":3049},{},[3050],{"type":46,"value":3051},"Beware the required-field deadlock.",{"type":46,"value":3053}," Adding a ",{"type":41,"tag":1413,"props":3055,"children":3056},{},[3057],{"type":46,"value":3058},"required",{"type":46,"value":3060}," field to a populated table fails the push — and a failed push blocks ",{"type":41,"tag":87,"props":3062,"children":3063},{},[3064],{"type":46,"value":3065},"ALL",{"type":46,"value":3067}," function deploys, including the very cleanup\u002Fbackfill mutation you'd write to fix it. Don't paint yourself into this corner: either widen→migrate→narrow (add it ",{"type":41,"tag":49,"props":3069,"children":3071},{"className":3070},[],[3072],{"type":46,"value":3073},"v.optional",{"type":46,"value":3075},", backfill or clear rows, ",{"type":41,"tag":1413,"props":3077,"children":3078},{},[3079],{"type":46,"value":3080},"then",{"type":46,"value":3082}," make it required) or wipe the table first via ",{"type":41,"tag":49,"props":3084,"children":3086},{"className":3085},[],[3087],{"type":46,"value":3088},"npx convex import --replace",{"type":46,"value":3090}," of an empty file. Never add a bare required field to a table that already has rows.",{"type":41,"tag":127,"props":3092,"children":3093},{},[3094,3096,3102],{"type":46,"value":3095},"Schema errors show up in ",{"type":41,"tag":49,"props":3097,"children":3099},{"className":3098},[],[3100],{"type":46,"value":3101},"convex dev",{"type":46,"value":3103}," stdout. Read the message; don't guess.",{"type":41,"tag":127,"props":3105,"children":3106},{},[3107,3112,3114,3120,3122,3127,3129,3134,3136,3142,3143,3149,3151,3157,3158,3164,3166,3172],{"type":41,"tag":87,"props":3108,"children":3109},{},[3110],{"type":46,"value":3111},"dev→prod data migration:",{"type":46,"value":3113}," use a full-snapshot ",{"type":41,"tag":49,"props":3115,"children":3117},{"className":3116},[],[3118],{"type":46,"value":3119},"npx convex export",{"type":46,"value":3121}," → ",{"type":41,"tag":49,"props":3123,"children":3125},{"className":3124},[],[3126],{"type":46,"value":3088},{"type":46,"value":3128}," (not per-table — that re-ids rows and breaks foreign keys; snapshot import preserves ",{"type":41,"tag":49,"props":3130,"children":3132},{"className":3131},[],[3133],{"type":46,"value":2417},{"type":46,"value":3135},"). Carry the ",{"type":41,"tag":49,"props":3137,"children":3139},{"className":3138},[],[3140],{"type":46,"value":3141},"users",{"type":46,"value":264},{"type":41,"tag":49,"props":3144,"children":3146},{"className":3145},[],[3147],{"type":46,"value":3148},"auth*",{"type":46,"value":3150}," tables too so ownership resolves. Use ",{"type":41,"tag":49,"props":3152,"children":3154},{"className":3153},[],[3155],{"type":46,"value":3156},"--replace",{"type":46,"value":1366},{"type":41,"tag":49,"props":3159,"children":3161},{"className":3160},[],[3162],{"type":46,"value":3163},"--replace-all",{"type":46,"value":3165},", if any component (e.g. ",{"type":41,"tag":49,"props":3167,"children":3169},{"className":3168},[],[3170],{"type":46,"value":3171},"@convex-dev\u002Fstatic-hosting",{"type":46,"value":3173},") has tables in the snapshot you don't want wiped.",{"type":41,"tag":1582,"props":3175,"children":3177},{"id":3176},"resource-limits-design-around-them",[3178],{"type":46,"value":3179},"Resource limits — design around them",{"type":41,"tag":335,"props":3181,"children":3182},{},[3183,3199],{"type":41,"tag":339,"props":3184,"children":3185},{},[3186],{"type":41,"tag":343,"props":3187,"children":3188},{},[3189,3194],{"type":41,"tag":347,"props":3190,"children":3191},{},[3192],{"type":46,"value":3193},"Limit",{"type":41,"tag":347,"props":3195,"children":3196},{},[3197],{"type":46,"value":3198},"Value",{"type":41,"tag":358,"props":3200,"children":3201},{},[3202,3215,3228,3241,3254,3267],{"type":41,"tag":343,"props":3203,"children":3204},{},[3205,3210],{"type":41,"tag":365,"props":3206,"children":3207},{},[3208],{"type":46,"value":3209},"Reads per function",{"type":41,"tag":365,"props":3211,"children":3212},{},[3213],{"type":46,"value":3214},"~16,000 documents",{"type":41,"tag":343,"props":3216,"children":3217},{},[3218,3223],{"type":41,"tag":365,"props":3219,"children":3220},{},[3221],{"type":46,"value":3222},"Writes per function",{"type":41,"tag":365,"props":3224,"children":3225},{},[3226],{"type":46,"value":3227},"~8,000 documents",{"type":41,"tag":343,"props":3229,"children":3230},{},[3231,3236],{"type":41,"tag":365,"props":3232,"children":3233},{},[3234],{"type":46,"value":3235},"Single document",{"type":41,"tag":365,"props":3237,"children":3238},{},[3239],{"type":46,"value":3240},"1 MiB",{"type":41,"tag":343,"props":3242,"children":3243},{},[3244,3249],{"type":41,"tag":365,"props":3245,"children":3246},{},[3247],{"type":46,"value":3248},"Total payload",{"type":41,"tag":365,"props":3250,"children":3251},{},[3252],{"type":46,"value":3253},"8 MiB",{"type":41,"tag":343,"props":3255,"children":3256},{},[3257,3262],{"type":41,"tag":365,"props":3258,"children":3259},{},[3260],{"type":46,"value":3261},"Query CPU",{"type":41,"tag":365,"props":3263,"children":3264},{},[3265],{"type":46,"value":3266},"~1 second",{"type":41,"tag":343,"props":3268,"children":3269},{},[3270,3275],{"type":41,"tag":365,"props":3271,"children":3272},{},[3273],{"type":46,"value":3274},"Action runtime",{"type":41,"tag":365,"props":3276,"children":3277},{},[3278],{"type":46,"value":3279},"10 minutes",{"type":41,"tag":42,"props":3281,"children":3282},{},[3283,3285,3291,3293,3299,3301,3307,3309,3315],{"type":46,"value":3284},"Hitting a limit = redesign, not retry. Paginate (",{"type":41,"tag":49,"props":3286,"children":3288},{"className":3287},[],[3289],{"type":46,"value":3290},"paginationOptsValidator",{"type":46,"value":3292}," + ",{"type":41,"tag":49,"props":3294,"children":3296},{"className":3295},[],[3297],{"type":46,"value":3298},".paginate",{"type":46,"value":3300},"), batch via ",{"type":41,"tag":49,"props":3302,"children":3304},{"className":3303},[],[3305],{"type":46,"value":3306},"ctx.scheduler",{"type":46,"value":3308},", or use ",{"type":41,"tag":49,"props":3310,"children":3312},{"className":3311},[],[3313],{"type":46,"value":3314},"@convex-dev\u002Fworkpool",{"type":46,"value":3316}," for bounded concurrency.",{"type":41,"tag":1582,"props":3318,"children":3320},{"id":3319},"reactclient-patterns",[3321],{"type":46,"value":3322},"React\u002Fclient patterns",{"type":41,"tag":123,"props":3324,"children":3325},{},[3326,3349,3372],{"type":41,"tag":127,"props":3327,"children":3328},{},[3329,3339,3341,3347],{"type":41,"tag":87,"props":3330,"children":3331},{},[3332,3337],{"type":41,"tag":49,"props":3333,"children":3335},{"className":3334},[],[3336],{"type":46,"value":2667},{"type":46,"value":3338}," is reactive.",{"type":46,"value":3340}," Never wrap it in ",{"type":41,"tag":49,"props":3342,"children":3344},{"className":3343},[],[3345],{"type":46,"value":3346},"useEffect",{"type":46,"value":3348}," to refetch.",{"type":41,"tag":127,"props":3350,"children":3351},{},[3352,3363,3365,3371],{"type":41,"tag":87,"props":3353,"children":3354},{},[3355,3357],{"type":46,"value":3356},"Conditional fetches use ",{"type":41,"tag":49,"props":3358,"children":3360},{"className":3359},[],[3361],{"type":46,"value":3362},"\"skip\"",{"type":46,"value":3364},": ",{"type":41,"tag":49,"props":3366,"children":3368},{"className":3367},[],[3369],{"type":46,"value":3370},"useQuery(api.foo.bar, shouldFetch ? args : \"skip\")",{"type":46,"value":230},{"type":41,"tag":127,"props":3373,"children":3374},{},[3375,3380,3382,3388,3390,3396],{"type":41,"tag":87,"props":3376,"children":3377},{},[3378],{"type":46,"value":3379},"Mutations are transactional.",{"type":46,"value":3381}," Don't lock rows manually. OCC handles conflicts; if ",{"type":41,"tag":49,"props":3383,"children":3385},{"className":3384},[],[3386],{"type":46,"value":3387},"OCC conflict",{"type":46,"value":3389}," errors appear, reduce write contention (sharded counters via ",{"type":41,"tag":49,"props":3391,"children":3393},{"className":3392},[],[3394],{"type":46,"value":3395},"@convex-dev\u002Faggregate",{"type":46,"value":183},{"type":41,"tag":1582,"props":3398,"children":3400},{"id":3399},"auth",[3401],{"type":46,"value":3402},"Auth",{"type":41,"tag":123,"props":3404,"children":3405},{},[3406,3424,3484,3571,3609,3756,5186,5225,5442],{"type":41,"tag":127,"props":3407,"children":3408},{},[3409,3415,3417,3422],{"type":41,"tag":49,"props":3410,"children":3412},{"className":3411},[],[3413],{"type":46,"value":3414},"await ctx.auth.getUserIdentity()",{"type":46,"value":3416}," in any function that requires login. Returns ",{"type":41,"tag":49,"props":3418,"children":3420},{"className":3419},[],[3421],{"type":46,"value":2591},{"type":46,"value":3423}," if unauthenticated — handle both branches.",{"type":41,"tag":127,"props":3425,"children":3426},{},[3427,3432,3433,3439,3441,3446,3447,3452,3454,3460,3461,3467,3468,3474,3476,3482],{"type":41,"tag":87,"props":3428,"children":3429},{},[3430],{"type":46,"value":3431},"Check every mutation independently — don't generalize an auth check across a file.",{"type":46,"value":514},{"type":41,"tag":49,"props":3434,"children":3436},{"className":3435},[],[3437],{"type":46,"value":3438},"ctx.auth",{"type":46,"value":3440},"\u002Fownership check present in one mutation does not mean a sibling mutation in the same file is also covered; each public ",{"type":41,"tag":49,"props":3442,"children":3444},{"className":3443},[],[3445],{"type":46,"value":381},{"type":46,"value":264},{"type":41,"tag":49,"props":3448,"children":3450},{"className":3449},[],[3451],{"type":46,"value":373},{"type":46,"value":3453}," that trusts a client-supplied ",{"type":41,"tag":49,"props":3455,"children":3457},{"className":3456},[],[3458],{"type":46,"value":3459},"userId",{"type":46,"value":264},{"type":41,"tag":49,"props":3462,"children":3464},{"className":3463},[],[3465],{"type":46,"value":3466},"authorId",{"type":46,"value":264},{"type":41,"tag":49,"props":3469,"children":3471},{"className":3470},[],[3472],{"type":46,"value":3473},"ownerId",{"type":46,"value":3475}," arg without an independent ",{"type":41,"tag":49,"props":3477,"children":3479},{"className":3478},[],[3480],{"type":46,"value":3481},"ctx.auth.getUserIdentity()",{"type":46,"value":3483}," call (or a comparison against it) is a separate authz bug, even next to a function that does it right. This is a common false-negative: a model that sees one correct check in a file assumes the pattern applies everywhere and skips it on the next mutation down.",{"type":41,"tag":127,"props":3485,"children":3486},{},[3487,3534,3535,3540,3542,3547,3549,3555,3556,3562,3563,3569],{"type":41,"tag":87,"props":3488,"children":3489},{},[3490,3492,3498,3499,3505,3506,3512,3513,3519,3521,3526,3528,3533],{"type":46,"value":3491},"An \"operate on this ID\" mutation (",{"type":41,"tag":49,"props":3493,"children":3495},{"className":3494},[],[3496],{"type":46,"value":3497},"cancel",{"type":46,"value":375},{"type":41,"tag":49,"props":3500,"children":3502},{"className":3501},[],[3503],{"type":46,"value":3504},"edit",{"type":46,"value":375},{"type":41,"tag":49,"props":3507,"children":3509},{"className":3508},[],[3510],{"type":46,"value":3511},"setStatus",{"type":46,"value":375},{"type":41,"tag":49,"props":3514,"children":3516},{"className":3515},[],[3517],{"type":46,"value":3518},"accept",{"type":46,"value":3520},") needs an ownership check on the ",{"type":41,"tag":1413,"props":3522,"children":3523},{},[3524],{"type":46,"value":3525},"document",{"type":46,"value":3527},", not just an identity check on the ",{"type":41,"tag":1413,"props":3529,"children":3530},{},[3531],{"type":46,"value":3532},"caller",{"type":46,"value":230},{"type":46,"value":682},{"type":41,"tag":49,"props":3536,"children":3538},{"className":3537},[],[3539],{"type":46,"value":3481},{"type":46,"value":3541}," proves who's calling; it doesn't prove they're allowed to touch the specific row an ",{"type":41,"tag":49,"props":3543,"children":3545},{"className":3544},[],[3546],{"type":46,"value":2417},{"type":46,"value":3548}," argument points at. ",{"type":41,"tag":49,"props":3550,"children":3552},{"className":3551},[],[3553],{"type":46,"value":3554},"cancelAppointment(appointmentId)",{"type":46,"value":375},{"type":41,"tag":49,"props":3557,"children":3559},{"className":3558},[],[3560],{"type":46,"value":3561},"editAnswer(answerId, body)",{"type":46,"value":375},{"type":41,"tag":49,"props":3564,"children":3566},{"className":3565},[],[3567],{"type":46,"value":3568},"setOrderStatus(orderId, status)",{"type":46,"value":3570}," must load the doc and compare its owner\u002Fauthor field against the authenticated identity before mutating — otherwise any logged-in user can act on any other user's row just by supplying its id (which is often guessable or visible in a list response).",{"type":41,"tag":127,"props":3572,"children":3573},{},[3574,3579,3580,3586,3587,3593,3594,3600,3602,3607],{"type":41,"tag":87,"props":3575,"children":3576},{},[3577],{"type":46,"value":3578},"A query returning another party's private data (PII, order history, revenue, audit logs) by an argument the client supplies is a leak, even if it \"looks read-only.\"",{"type":46,"value":682},{"type":41,"tag":49,"props":3581,"children":3583},{"className":3582},[],[3584],{"type":46,"value":3585},"getUserOrders(userId)",{"type":46,"value":375},{"type":41,"tag":49,"props":3588,"children":3590},{"className":3589},[],[3591],{"type":46,"value":3592},"getSellerDashboard(shopId)",{"type":46,"value":375},{"type":41,"tag":49,"props":3595,"children":3597},{"className":3596},[],[3598],{"type":46,"value":3599},"listAuditLogForStaff(staffId)",{"type":46,"value":3601}," are exploitable the same way an unauthenticated mutation is: derive the subject from ",{"type":41,"tag":49,"props":3603,"children":3605},{"className":3604},[],[3606],{"type":46,"value":3438},{"type":46,"value":3608},", not from the argument, or verify the argument matches the authenticated identity before returning anything.",{"type":41,"tag":127,"props":3610,"children":3611},{},[3612,3624,3625,3631,3632,3638,3640,3646,3648,3653,3655,3661,3662,3668,3670,3675,3677,3682,3683,3688,3690,3695,3697,3702,3704,3708,3710,3715,3716,3721,3723,3728,3729,3734,3736,3742,3744,3749,3750,3755],{"type":41,"tag":87,"props":3613,"children":3614},{},[3615,3617,3622],{"type":46,"value":3616},"MANDATORY FIRST STEP — check the auth foundation exists before injecting any ",{"type":41,"tag":49,"props":3618,"children":3620},{"className":3619},[],[3621],{"type":46,"value":3438},{"type":46,"value":3623}," enforcement.",{"type":46,"value":682},{"type":41,"tag":49,"props":3626,"children":3628},{"className":3627},[],[3629],{"type":46,"value":3630},"requireIdentity",{"type":46,"value":264},{"type":41,"tag":49,"props":3633,"children":3635},{"className":3634},[],[3636],{"type":46,"value":3637},"requireOwner",{"type":46,"value":3639}," (below) only work when the app actually has auth wired up: (1) is there an ",{"type":41,"tag":49,"props":3641,"children":3643},{"className":3642},[],[3644],{"type":46,"value":3645},"auth.config.ts",{"type":46,"value":3647}," with a provider? (2) is there a ",{"type":41,"tag":49,"props":3649,"children":3651},{"className":3650},[],[3652],{"type":46,"value":3141},{"type":46,"value":3654},"\u002Fidentities table keyed to the auth subject (",{"type":41,"tag":49,"props":3656,"children":3658},{"className":3657},[],[3659],{"type":46,"value":3660},"tokenIdentifier",{"type":46,"value":264},{"type":41,"tag":49,"props":3663,"children":3665},{"className":3664},[],[3666],{"type":46,"value":3667},"identity.subject",{"type":46,"value":3669},")? If EITHER is missing, do ",{"type":41,"tag":87,"props":3671,"children":3672},{},[3673],{"type":46,"value":3674},"not",{"type":46,"value":3676}," add ",{"type":41,"tag":49,"props":3678,"children":3680},{"className":3679},[],[3681],{"type":46,"value":3630},{"type":46,"value":264},{"type":41,"tag":49,"props":3684,"children":3686},{"className":3685},[],[3687],{"type":46,"value":3637},{"type":46,"value":3689}," — on a foundationless app ",{"type":41,"tag":49,"props":3691,"children":3693},{"className":3692},[],[3694],{"type":46,"value":3481},{"type":46,"value":3696}," always returns ",{"type":41,"tag":49,"props":3698,"children":3700},{"className":3699},[],[3701],{"type":46,"value":2591},{"type":46,"value":3703},", so the \"fix\" either 401s every caller (non-functional) or gets miscompared against some other client-supplied field like an email string (mismatched) — this is a ",{"type":41,"tag":1413,"props":3705,"children":3706},{},[3707],{"type":46,"value":711},{"type":46,"value":3709}," authz defect, not a repair, and a reviewer will correctly flag it as one. Instead, on a foundationless app: (a) for privileged\u002Fadmin operations, convert the public ",{"type":41,"tag":49,"props":3711,"children":3713},{"className":3712},[],[3714],{"type":46,"value":373},{"type":46,"value":264},{"type":41,"tag":49,"props":3717,"children":3719},{"className":3718},[],[3720],{"type":46,"value":381},{"type":46,"value":3722}," to ",{"type":41,"tag":49,"props":3724,"children":3726},{"className":3725},[],[3727],{"type":46,"value":395},{"type":46,"value":264},{"type":41,"tag":49,"props":3730,"children":3732},{"className":3731},[],[3733],{"type":46,"value":402},{"type":46,"value":3735}," — this removes public reachability entirely, which is safe and requires no auth foundation; (b) tell the user: \"this app has no auth foundation; run ",{"type":41,"tag":49,"props":3737,"children":3739},{"className":3738},[],[3740],{"type":46,"value":3741},"\u002Fadd auth",{"type":46,"value":3743}," or the auth setup first, then re-run convex-authz to add per-user ownership checks.\" Only once both the provider config and the subject-keyed users table exist should you proceed to inject ",{"type":41,"tag":49,"props":3745,"children":3747},{"className":3746},[],[3748],{"type":46,"value":3630},{"type":46,"value":264},{"type":41,"tag":49,"props":3751,"children":3753},{"className":3752},[],[3754],{"type":46,"value":3637},{"type":46,"value":230},{"type":41,"tag":127,"props":3757,"children":3758},{},[3759,3764,3766,3771,3772,4421,4424,4429,4431,4436,4438,4444,4445,4451,4452,4458,4460,4466,4468,4473,4475,4481,4483,4488,4490,4496,4497,4502,4504,4510,4512,4518,4519,4935,4938,4940],{"type":41,"tag":87,"props":3760,"children":3761},{},[3762],{"type":46,"value":3763},"Copy this pattern instead of re-deriving it per function.",{"type":46,"value":3765}," The rules above (identity-from-arg, missing ownership check, PII-by-argument) are the single largest real-defect cluster measured against generated Convex backends — 44 of 214 confirmed defects in one 30-app corpus. Two small helpers close them at once — ",{"type":41,"tag":87,"props":3767,"children":3768},{},[3769],{"type":46,"value":3770},"but only once the foundation check above passes",{"type":46,"value":1776},{"type":41,"tag":1589,"props":3773,"children":3775},{"className":1591,"code":3774,"language":1593,"meta":1594,"style":1594},"\u002F\u002F convex\u002Fmodel\u002Fauth.ts\nimport { QueryCtx, MutationCtx } from \"..\u002F_generated\u002Fserver\";\n\n\u002F** Throws if unauthenticated. Never trust a client-supplied userId instead. *\u002F\nexport async function requireIdentity(ctx: QueryCtx | MutationCtx) {\n  const identity = await ctx.auth.getUserIdentity();\n  if (!identity) throw new Error(\"401: not signed in\");\n  return identity; \u002F\u002F identity.subject is the stable per-user id\n}\n\n\u002F** Loads `doc` by id and throws unless its owner field matches the caller. *\u002F\nexport async function requireOwner\u003CT extends { ownerId: string }>(\n  ctx: QueryCtx | MutationCtx,\n  doc: T | null,\n): Promise\u003CT> {\n  if (!doc) throw new Error(\"404: not found\");\n  const identity = await requireIdentity(ctx);\n  if (doc.ownerId !== identity.subject) throw new Error(\"403: forbidden\");\n  return doc;\n}\n",[3776],{"type":41,"tag":49,"props":3777,"children":3778},{"__ignoreMap":1594},[3779,3788,3838,3845,3853,3908,3958,4025,4047,4055,4062,4070,4128,4156,4186,4216,4277,4316,4398,4414],{"type":41,"tag":1600,"props":3780,"children":3781},{"class":1602,"line":1603},[3782],{"type":41,"tag":1600,"props":3783,"children":3785},{"style":3784},"--shiki-light:#90A4AE;--shiki-light-font-style:italic;--shiki-default:#546E7A;--shiki-default-font-style:italic;--shiki-dark:#676E95;--shiki-dark-font-style:italic",[3786],{"type":46,"value":3787},"\u002F\u002F convex\u002Fmodel\u002Fauth.ts\n",{"type":41,"tag":1600,"props":3789,"children":3790},{"class":1602,"line":24},[3791,3795,3799,3804,3808,3813,3817,3821,3825,3830,3834],{"type":41,"tag":1600,"props":3792,"children":3793},{"style":1607},[3794],{"type":46,"value":739},{"type":41,"tag":1600,"props":3796,"children":3797},{"style":1612},[3798],{"type":46,"value":1615},{"type":41,"tag":1600,"props":3800,"children":3801},{"style":1618},[3802],{"type":46,"value":3803}," QueryCtx",{"type":41,"tag":1600,"props":3805,"children":3806},{"style":1612},[3807],{"type":46,"value":1673},{"type":41,"tag":1600,"props":3809,"children":3810},{"style":1618},[3811],{"type":46,"value":3812}," MutationCtx",{"type":41,"tag":1600,"props":3814,"children":3815},{"style":1612},[3816],{"type":46,"value":1626},{"type":41,"tag":1600,"props":3818,"children":3819},{"style":1607},[3820],{"type":46,"value":1631},{"type":41,"tag":1600,"props":3822,"children":3823},{"style":1612},[3824],{"type":46,"value":1636},{"type":41,"tag":1600,"props":3826,"children":3827},{"style":1639},[3828],{"type":46,"value":3829},"..\u002F_generated\u002Fserver",{"type":41,"tag":1600,"props":3831,"children":3832},{"style":1612},[3833],{"type":46,"value":1647},{"type":41,"tag":1600,"props":3835,"children":3836},{"style":1612},[3837],{"type":46,"value":1652},{"type":41,"tag":1600,"props":3839,"children":3840},{"class":1602,"line":1714},[3841],{"type":41,"tag":1600,"props":3842,"children":3843},{"emptyLinePlaceholder":1718},[3844],{"type":46,"value":1721},{"type":41,"tag":1600,"props":3846,"children":3847},{"class":1602,"line":1724},[3848],{"type":41,"tag":1600,"props":3849,"children":3850},{"style":3784},[3851],{"type":46,"value":3852},"\u002F** Throws if unauthenticated. Never trust a client-supplied userId instead. *\u002F\n",{"type":41,"tag":1600,"props":3854,"children":3855},{"class":1602,"line":1764},[3856,3860,3864,3869,3874,3878,3882,3886,3891,3896,3900,3904],{"type":41,"tag":1600,"props":3857,"children":3858},{"style":1607},[3859],{"type":46,"value":1730},{"type":41,"tag":1600,"props":3861,"children":3862},{"style":1733},[3863],{"type":46,"value":2052},{"type":41,"tag":1600,"props":3865,"children":3866},{"style":1733},[3867],{"type":46,"value":3868}," function",{"type":41,"tag":1600,"props":3870,"children":3871},{"style":1749},[3872],{"type":46,"value":3873}," requireIdentity",{"type":41,"tag":1600,"props":3875,"children":3876},{"style":1612},[3877],{"type":46,"value":1756},{"type":41,"tag":1600,"props":3879,"children":3880},{"style":2060},[3881],{"type":46,"value":2063},{"type":41,"tag":1600,"props":3883,"children":3884},{"style":1612},[3885],{"type":46,"value":1776},{"type":41,"tag":1600,"props":3887,"children":3889},{"style":3888},"--shiki-light:#E2931D;--shiki-default:#FFCB6B;--shiki-dark:#FFCB6B",[3890],{"type":46,"value":3803},{"type":41,"tag":1600,"props":3892,"children":3893},{"style":1612},[3894],{"type":46,"value":3895}," |",{"type":41,"tag":1600,"props":3897,"children":3898},{"style":3888},[3899],{"type":46,"value":3812},{"type":41,"tag":1600,"props":3901,"children":3902},{"style":1612},[3903],{"type":46,"value":1931},{"type":41,"tag":1600,"props":3905,"children":3906},{"style":1612},[3907],{"type":46,"value":2086},{"type":41,"tag":1600,"props":3909,"children":3910},{"class":1602,"line":1829},[3911,3916,3921,3925,3929,3933,3937,3941,3945,3950,3954],{"type":41,"tag":1600,"props":3912,"children":3913},{"style":1733},[3914],{"type":46,"value":3915},"  const",{"type":41,"tag":1600,"props":3917,"children":3918},{"style":1618},[3919],{"type":46,"value":3920}," identity",{"type":41,"tag":1600,"props":3922,"children":3923},{"style":1612},[3924],{"type":46,"value":2105},{"type":41,"tag":1600,"props":3926,"children":3927},{"style":1607},[3928],{"type":46,"value":2110},{"type":41,"tag":1600,"props":3930,"children":3931},{"style":1618},[3932],{"type":46,"value":2115},{"type":41,"tag":1600,"props":3934,"children":3935},{"style":1612},[3936],{"type":46,"value":230},{"type":41,"tag":1600,"props":3938,"children":3939},{"style":1618},[3940],{"type":46,"value":3399},{"type":41,"tag":1600,"props":3942,"children":3943},{"style":1612},[3944],{"type":46,"value":230},{"type":41,"tag":1600,"props":3946,"children":3947},{"style":1749},[3948],{"type":46,"value":3949},"getUserIdentity",{"type":41,"tag":1600,"props":3951,"children":3952},{"style":1768},[3953],{"type":46,"value":1966},{"type":41,"tag":1600,"props":3955,"children":3956},{"style":1612},[3957],{"type":46,"value":1652},{"type":41,"tag":1600,"props":3959,"children":3960},{"class":1602,"line":1860},[3961,3966,3970,3975,3980,3985,3990,3995,4000,4004,4008,4013,4017,4021],{"type":41,"tag":1600,"props":3962,"children":3963},{"style":1607},[3964],{"type":46,"value":3965},"  if",{"type":41,"tag":1600,"props":3967,"children":3968},{"style":1768},[3969],{"type":46,"value":2057},{"type":41,"tag":1600,"props":3971,"children":3972},{"style":1612},[3973],{"type":46,"value":3974},"!",{"type":41,"tag":1600,"props":3976,"children":3977},{"style":1618},[3978],{"type":46,"value":3979},"identity",{"type":41,"tag":1600,"props":3981,"children":3982},{"style":1768},[3983],{"type":46,"value":3984},") ",{"type":41,"tag":1600,"props":3986,"children":3987},{"style":1607},[3988],{"type":46,"value":3989},"throw",{"type":41,"tag":1600,"props":3991,"children":3992},{"style":1612},[3993],{"type":46,"value":3994}," new",{"type":41,"tag":1600,"props":3996,"children":3997},{"style":1749},[3998],{"type":46,"value":3999}," Error",{"type":41,"tag":1600,"props":4001,"children":4002},{"style":1768},[4003],{"type":46,"value":1756},{"type":41,"tag":1600,"props":4005,"children":4006},{"style":1612},[4007],{"type":46,"value":1647},{"type":41,"tag":1600,"props":4009,"children":4010},{"style":1639},[4011],{"type":46,"value":4012},"401: not signed in",{"type":41,"tag":1600,"props":4014,"children":4015},{"style":1612},[4016],{"type":46,"value":1647},{"type":41,"tag":1600,"props":4018,"children":4019},{"style":1768},[4020],{"type":46,"value":1931},{"type":41,"tag":1600,"props":4022,"children":4023},{"style":1612},[4024],{"type":46,"value":1652},{"type":41,"tag":1600,"props":4026,"children":4027},{"class":1602,"line":1886},[4028,4033,4037,4042],{"type":41,"tag":1600,"props":4029,"children":4030},{"style":1607},[4031],{"type":46,"value":4032},"  return",{"type":41,"tag":1600,"props":4034,"children":4035},{"style":1618},[4036],{"type":46,"value":3920},{"type":41,"tag":1600,"props":4038,"children":4039},{"style":1612},[4040],{"type":46,"value":4041},";",{"type":41,"tag":1600,"props":4043,"children":4044},{"style":3784},[4045],{"type":46,"value":4046}," \u002F\u002F identity.subject is the stable per-user id\n",{"type":41,"tag":1600,"props":4048,"children":4049},{"class":1602,"line":1939},[4050],{"type":41,"tag":1600,"props":4051,"children":4052},{"style":1612},[4053],{"type":46,"value":4054},"}\n",{"type":41,"tag":1600,"props":4056,"children":4057},{"class":1602,"line":1973},[4058],{"type":41,"tag":1600,"props":4059,"children":4060},{"emptyLinePlaceholder":1718},[4061],{"type":46,"value":1721},{"type":41,"tag":1600,"props":4063,"children":4064},{"class":1602,"line":2007},[4065],{"type":41,"tag":1600,"props":4066,"children":4067},{"style":3784},[4068],{"type":46,"value":4069},"\u002F** Loads `doc` by id and throws unless its owner field matches the caller. *\u002F\n",{"type":41,"tag":1600,"props":4071,"children":4072},{"class":1602,"line":2024},[4073,4077,4081,4085,4090,4095,4100,4105,4109,4114,4118,4123],{"type":41,"tag":1600,"props":4074,"children":4075},{"style":1607},[4076],{"type":46,"value":1730},{"type":41,"tag":1600,"props":4078,"children":4079},{"style":1733},[4080],{"type":46,"value":2052},{"type":41,"tag":1600,"props":4082,"children":4083},{"style":1733},[4084],{"type":46,"value":3868},{"type":41,"tag":1600,"props":4086,"children":4087},{"style":1749},[4088],{"type":46,"value":4089}," requireOwner",{"type":41,"tag":1600,"props":4091,"children":4092},{"style":1612},[4093],{"type":46,"value":4094},"\u003C",{"type":41,"tag":1600,"props":4096,"children":4097},{"style":3888},[4098],{"type":46,"value":4099},"T",{"type":41,"tag":1600,"props":4101,"children":4102},{"style":1733},[4103],{"type":46,"value":4104}," extends",{"type":41,"tag":1600,"props":4106,"children":4107},{"style":1612},[4108],{"type":46,"value":1615},{"type":41,"tag":1600,"props":4110,"children":4111},{"style":1768},[4112],{"type":46,"value":4113}," ownerId",{"type":41,"tag":1600,"props":4115,"children":4116},{"style":1612},[4117],{"type":46,"value":1776},{"type":41,"tag":1600,"props":4119,"children":4120},{"style":3888},[4121],{"type":46,"value":4122}," string",{"type":41,"tag":1600,"props":4124,"children":4125},{"style":1612},[4126],{"type":46,"value":4127}," }>(\n",{"type":41,"tag":1600,"props":4129,"children":4130},{"class":1602,"line":2037},[4131,4136,4140,4144,4148,4152],{"type":41,"tag":1600,"props":4132,"children":4133},{"style":2060},[4134],{"type":46,"value":4135},"  ctx",{"type":41,"tag":1600,"props":4137,"children":4138},{"style":1612},[4139],{"type":46,"value":1776},{"type":41,"tag":1600,"props":4141,"children":4142},{"style":3888},[4143],{"type":46,"value":3803},{"type":41,"tag":1600,"props":4145,"children":4146},{"style":1612},[4147],{"type":46,"value":3895},{"type":41,"tag":1600,"props":4149,"children":4150},{"style":3888},[4151],{"type":46,"value":3812},{"type":41,"tag":1600,"props":4153,"children":4154},{"style":1612},[4155],{"type":46,"value":1936},{"type":41,"tag":1600,"props":4157,"children":4158},{"class":1602,"line":2089},[4159,4164,4168,4173,4177,4182],{"type":41,"tag":1600,"props":4160,"children":4161},{"style":2060},[4162],{"type":46,"value":4163},"  doc",{"type":41,"tag":1600,"props":4165,"children":4166},{"style":1612},[4167],{"type":46,"value":1776},{"type":41,"tag":1600,"props":4169,"children":4170},{"style":3888},[4171],{"type":46,"value":4172}," T",{"type":41,"tag":1600,"props":4174,"children":4175},{"style":1612},[4176],{"type":46,"value":3895},{"type":41,"tag":1600,"props":4178,"children":4179},{"style":3888},[4180],{"type":46,"value":4181}," null",{"type":41,"tag":1600,"props":4183,"children":4184},{"style":1612},[4185],{"type":46,"value":1936},{"type":41,"tag":1600,"props":4187,"children":4188},{"class":1602,"line":2127},[4189,4194,4199,4203,4207,4212],{"type":41,"tag":1600,"props":4190,"children":4191},{"style":1612},[4192],{"type":46,"value":4193},"):",{"type":41,"tag":1600,"props":4195,"children":4196},{"style":3888},[4197],{"type":46,"value":4198}," Promise",{"type":41,"tag":1600,"props":4200,"children":4201},{"style":1612},[4202],{"type":46,"value":4094},{"type":41,"tag":1600,"props":4204,"children":4205},{"style":3888},[4206],{"type":46,"value":4099},{"type":41,"tag":1600,"props":4208,"children":4209},{"style":1612},[4210],{"type":46,"value":4211},">",{"type":41,"tag":1600,"props":4213,"children":4214},{"style":1612},[4215],{"type":46,"value":2086},{"type":41,"tag":1600,"props":4217,"children":4218},{"class":1602,"line":2161},[4219,4223,4227,4231,4236,4240,4244,4248,4252,4256,4260,4265,4269,4273],{"type":41,"tag":1600,"props":4220,"children":4221},{"style":1607},[4222],{"type":46,"value":3965},{"type":41,"tag":1600,"props":4224,"children":4225},{"style":1768},[4226],{"type":46,"value":2057},{"type":41,"tag":1600,"props":4228,"children":4229},{"style":1612},[4230],{"type":46,"value":3974},{"type":41,"tag":1600,"props":4232,"children":4233},{"style":1618},[4234],{"type":46,"value":4235},"doc",{"type":41,"tag":1600,"props":4237,"children":4238},{"style":1768},[4239],{"type":46,"value":3984},{"type":41,"tag":1600,"props":4241,"children":4242},{"style":1607},[4243],{"type":46,"value":3989},{"type":41,"tag":1600,"props":4245,"children":4246},{"style":1612},[4247],{"type":46,"value":3994},{"type":41,"tag":1600,"props":4249,"children":4250},{"style":1749},[4251],{"type":46,"value":3999},{"type":41,"tag":1600,"props":4253,"children":4254},{"style":1768},[4255],{"type":46,"value":1756},{"type":41,"tag":1600,"props":4257,"children":4258},{"style":1612},[4259],{"type":46,"value":1647},{"type":41,"tag":1600,"props":4261,"children":4262},{"style":1639},[4263],{"type":46,"value":4264},"404: not found",{"type":41,"tag":1600,"props":4266,"children":4267},{"style":1612},[4268],{"type":46,"value":1647},{"type":41,"tag":1600,"props":4270,"children":4271},{"style":1768},[4272],{"type":46,"value":1931},{"type":41,"tag":1600,"props":4274,"children":4275},{"style":1612},[4276],{"type":46,"value":1652},{"type":41,"tag":1600,"props":4278,"children":4279},{"class":1602,"line":2263},[4280,4284,4288,4292,4296,4300,4304,4308,4312],{"type":41,"tag":1600,"props":4281,"children":4282},{"style":1733},[4283],{"type":46,"value":3915},{"type":41,"tag":1600,"props":4285,"children":4286},{"style":1618},[4287],{"type":46,"value":3920},{"type":41,"tag":1600,"props":4289,"children":4290},{"style":1612},[4291],{"type":46,"value":2105},{"type":41,"tag":1600,"props":4293,"children":4294},{"style":1607},[4295],{"type":46,"value":2110},{"type":41,"tag":1600,"props":4297,"children":4298},{"style":1749},[4299],{"type":46,"value":3873},{"type":41,"tag":1600,"props":4301,"children":4302},{"style":1768},[4303],{"type":46,"value":1756},{"type":41,"tag":1600,"props":4305,"children":4306},{"style":1618},[4307],{"type":46,"value":2063},{"type":41,"tag":1600,"props":4309,"children":4310},{"style":1768},[4311],{"type":46,"value":1931},{"type":41,"tag":1600,"props":4313,"children":4314},{"style":1612},[4315],{"type":46,"value":1652},{"type":41,"tag":1600,"props":4317,"children":4318},{"class":1602,"line":2297},[4319,4323,4327,4331,4335,4339,4344,4348,4352,4357,4361,4365,4369,4373,4377,4381,4386,4390,4394],{"type":41,"tag":1600,"props":4320,"children":4321},{"style":1607},[4322],{"type":46,"value":3965},{"type":41,"tag":1600,"props":4324,"children":4325},{"style":1768},[4326],{"type":46,"value":2057},{"type":41,"tag":1600,"props":4328,"children":4329},{"style":1618},[4330],{"type":46,"value":4235},{"type":41,"tag":1600,"props":4332,"children":4333},{"style":1612},[4334],{"type":46,"value":230},{"type":41,"tag":1600,"props":4336,"children":4337},{"style":1618},[4338],{"type":46,"value":3473},{"type":41,"tag":1600,"props":4340,"children":4341},{"style":1612},[4342],{"type":46,"value":4343}," !==",{"type":41,"tag":1600,"props":4345,"children":4346},{"style":1618},[4347],{"type":46,"value":3920},{"type":41,"tag":1600,"props":4349,"children":4350},{"style":1612},[4351],{"type":46,"value":230},{"type":41,"tag":1600,"props":4353,"children":4354},{"style":1618},[4355],{"type":46,"value":4356},"subject",{"type":41,"tag":1600,"props":4358,"children":4359},{"style":1768},[4360],{"type":46,"value":3984},{"type":41,"tag":1600,"props":4362,"children":4363},{"style":1607},[4364],{"type":46,"value":3989},{"type":41,"tag":1600,"props":4366,"children":4367},{"style":1612},[4368],{"type":46,"value":3994},{"type":41,"tag":1600,"props":4370,"children":4371},{"style":1749},[4372],{"type":46,"value":3999},{"type":41,"tag":1600,"props":4374,"children":4375},{"style":1768},[4376],{"type":46,"value":1756},{"type":41,"tag":1600,"props":4378,"children":4379},{"style":1612},[4380],{"type":46,"value":1647},{"type":41,"tag":1600,"props":4382,"children":4383},{"style":1639},[4384],{"type":46,"value":4385},"403: forbidden",{"type":41,"tag":1600,"props":4387,"children":4388},{"style":1612},[4389],{"type":46,"value":1647},{"type":41,"tag":1600,"props":4391,"children":4392},{"style":1768},[4393],{"type":46,"value":1931},{"type":41,"tag":1600,"props":4395,"children":4396},{"style":1612},[4397],{"type":46,"value":1652},{"type":41,"tag":1600,"props":4399,"children":4400},{"class":1602,"line":2345},[4401,4405,4410],{"type":41,"tag":1600,"props":4402,"children":4403},{"style":1607},[4404],{"type":46,"value":4032},{"type":41,"tag":1600,"props":4406,"children":4407},{"style":1618},[4408],{"type":46,"value":4409}," doc",{"type":41,"tag":1600,"props":4411,"children":4412},{"style":1612},[4413],{"type":46,"value":1652},{"type":41,"tag":1600,"props":4415,"children":4416},{"class":1602,"line":2485},[4417],{"type":41,"tag":1600,"props":4418,"children":4419},{"style":1612},[4420],{"type":46,"value":4054},{"type":41,"tag":448,"props":4422,"children":4423},{},[],{"type":41,"tag":87,"props":4425,"children":4426},{},[4427],{"type":46,"value":4428},"Match the comparison to what the schema actually stores in the owner field.",{"type":46,"value":4430}," The version above assumes ",{"type":41,"tag":49,"props":4432,"children":4434},{"className":4433},[],[4435],{"type":46,"value":3473},{"type":46,"value":4437}," holds the raw auth subject. Most schemas instead store an ",{"type":41,"tag":49,"props":4439,"children":4441},{"className":4440},[],[4442],{"type":46,"value":4443},"Id\u003C\"users\">",{"type":46,"value":2057},{"type":41,"tag":49,"props":4446,"children":4448},{"className":4447},[],[4449],{"type":46,"value":4450},"ownerId: v.id(\"users\")",{"type":46,"value":375},{"type":41,"tag":49,"props":4453,"children":4455},{"className":4454},[],[4456],{"type":46,"value":4457},"senderId: v.id(\"users\")",{"type":46,"value":4459},", a ",{"type":41,"tag":49,"props":4461,"children":4463},{"className":4462},[],[4464],{"type":46,"value":4465},"participantIds",{"type":46,"value":4467}," array) — there, comparing against ",{"type":41,"tag":49,"props":4469,"children":4471},{"className":4470},[],[4472],{"type":46,"value":3667},{"type":46,"value":4474}," NEVER matches and silently breaks enforcement (every legitimate owner gets 403, or the check gets loosened until it always passes). For those schemas, add a ",{"type":41,"tag":49,"props":4476,"children":4478},{"className":4477},[],[4479],{"type":46,"value":4480},"requireUser(ctx)",{"type":46,"value":4482}," step that resolves the caller's own ",{"type":41,"tag":49,"props":4484,"children":4486},{"className":4485},[],[4487],{"type":46,"value":3141},{"type":46,"value":4489}," row through the subject-keyed index (",{"type":41,"tag":49,"props":4491,"children":4493},{"className":4492},[],[4494],{"type":46,"value":4495},"by_token",{"type":46,"value":1152},{"type":41,"tag":49,"props":4498,"children":4500},{"className":4499},[],[4501],{"type":46,"value":3660},{"type":46,"value":4503},") and compare ",{"type":41,"tag":49,"props":4505,"children":4507},{"className":4506},[],[4508],{"type":46,"value":4509},"doc.ownerId !== user._id",{"type":46,"value":4511}," instead; for membership-array containers check ",{"type":41,"tag":49,"props":4513,"children":4515},{"className":4514},[],[4516],{"type":46,"value":4517},"participantIds.includes(user._id)",{"type":46,"value":230},{"type":41,"tag":1589,"props":4520,"children":4522},{"className":1591,"code":4521,"language":1593,"meta":1594,"style":1594},"\u002F\u002F convex\u002Fappointments.ts — the three rules applied together\nexport const cancel = mutation({\n  args: { appointmentId: v.id(\"appointments\") }, \u002F\u002F NOT `cancelledBy`\u002F`actorId` from the client\n  returns: v.null(),\n  handler: async (ctx, args) => {\n    const appointment = await ctx.db.get(args.appointmentId);\n    await requireOwner(ctx, appointment); \u002F\u002F ownership check on the DOCUMENT, not just \"is someone logged in\"\n    await ctx.db.patch(args.appointmentId, { status: \"cancelled\" });\n    return null;\n  },\n});\n",[4523],{"type":41,"tag":49,"props":4524,"children":4525},{"__ignoreMap":1594},[4526,4534,4566,4633,4664,4707,4774,4815,4901,4913,4920],{"type":41,"tag":1600,"props":4527,"children":4528},{"class":1602,"line":1603},[4529],{"type":41,"tag":1600,"props":4530,"children":4531},{"style":3784},[4532],{"type":46,"value":4533},"\u002F\u002F convex\u002Fappointments.ts — the three rules applied together\n",{"type":41,"tag":1600,"props":4535,"children":4536},{"class":1602,"line":24},[4537,4541,4545,4550,4554,4558,4562],{"type":41,"tag":1600,"props":4538,"children":4539},{"style":1607},[4540],{"type":46,"value":1730},{"type":41,"tag":1600,"props":4542,"children":4543},{"style":1733},[4544],{"type":46,"value":1736},{"type":41,"tag":1600,"props":4546,"children":4547},{"style":1618},[4548],{"type":46,"value":4549}," cancel ",{"type":41,"tag":1600,"props":4551,"children":4552},{"style":1612},[4553],{"type":46,"value":1746},{"type":41,"tag":1600,"props":4555,"children":4556},{"style":1749},[4557],{"type":46,"value":1678},{"type":41,"tag":1600,"props":4559,"children":4560},{"style":1618},[4561],{"type":46,"value":1756},{"type":41,"tag":1600,"props":4563,"children":4564},{"style":1612},[4565],{"type":46,"value":1761},{"type":41,"tag":1600,"props":4567,"children":4568},{"class":1602,"line":1714},[4569,4573,4577,4581,4586,4590,4594,4598,4602,4606,4610,4615,4619,4623,4628],{"type":41,"tag":1600,"props":4570,"children":4571},{"style":1768},[4572],{"type":46,"value":1771},{"type":41,"tag":1600,"props":4574,"children":4575},{"style":1612},[4576],{"type":46,"value":1776},{"type":41,"tag":1600,"props":4578,"children":4579},{"style":1612},[4580],{"type":46,"value":1615},{"type":41,"tag":1600,"props":4582,"children":4583},{"style":1768},[4584],{"type":46,"value":4585}," appointmentId",{"type":41,"tag":1600,"props":4587,"children":4588},{"style":1612},[4589],{"type":46,"value":1776},{"type":41,"tag":1600,"props":4591,"children":4592},{"style":1618},[4593],{"type":46,"value":1621},{"type":41,"tag":1600,"props":4595,"children":4596},{"style":1612},[4597],{"type":46,"value":230},{"type":41,"tag":1600,"props":4599,"children":4600},{"style":1749},[4601],{"type":46,"value":1909},{"type":41,"tag":1600,"props":4603,"children":4604},{"style":1618},[4605],{"type":46,"value":1756},{"type":41,"tag":1600,"props":4607,"children":4608},{"style":1612},[4609],{"type":46,"value":1647},{"type":41,"tag":1600,"props":4611,"children":4612},{"style":1639},[4613],{"type":46,"value":4614},"appointments",{"type":41,"tag":1600,"props":4616,"children":4617},{"style":1612},[4618],{"type":46,"value":1647},{"type":41,"tag":1600,"props":4620,"children":4621},{"style":1618},[4622],{"type":46,"value":3984},{"type":41,"tag":1600,"props":4624,"children":4625},{"style":1612},[4626],{"type":46,"value":4627},"},",{"type":41,"tag":1600,"props":4629,"children":4630},{"style":3784},[4631],{"type":46,"value":4632}," \u002F\u002F NOT `cancelledBy`\u002F`actorId` from the client\n",{"type":41,"tag":1600,"props":4634,"children":4635},{"class":1602,"line":1724},[4636,4640,4644,4648,4652,4656,4660],{"type":41,"tag":1600,"props":4637,"children":4638},{"style":1768},[4639],{"type":46,"value":1835},{"type":41,"tag":1600,"props":4641,"children":4642},{"style":1612},[4643],{"type":46,"value":1776},{"type":41,"tag":1600,"props":4645,"children":4646},{"style":1618},[4647],{"type":46,"value":1621},{"type":41,"tag":1600,"props":4649,"children":4650},{"style":1612},[4651],{"type":46,"value":230},{"type":41,"tag":1600,"props":4653,"children":4654},{"style":1749},[4655],{"type":46,"value":2591},{"type":41,"tag":1600,"props":4657,"children":4658},{"style":1618},[4659],{"type":46,"value":1966},{"type":41,"tag":1600,"props":4661,"children":4662},{"style":1612},[4663],{"type":46,"value":1936},{"type":41,"tag":1600,"props":4665,"children":4666},{"class":1602,"line":1764},[4667,4671,4675,4679,4683,4687,4691,4695,4699,4703],{"type":41,"tag":1600,"props":4668,"children":4669},{"style":1749},[4670],{"type":46,"value":2043},{"type":41,"tag":1600,"props":4672,"children":4673},{"style":1612},[4674],{"type":46,"value":1776},{"type":41,"tag":1600,"props":4676,"children":4677},{"style":1733},[4678],{"type":46,"value":2052},{"type":41,"tag":1600,"props":4680,"children":4681},{"style":1612},[4682],{"type":46,"value":2057},{"type":41,"tag":1600,"props":4684,"children":4685},{"style":2060},[4686],{"type":46,"value":2063},{"type":41,"tag":1600,"props":4688,"children":4689},{"style":1612},[4690],{"type":46,"value":1673},{"type":41,"tag":1600,"props":4692,"children":4693},{"style":2060},[4694],{"type":46,"value":2072},{"type":41,"tag":1600,"props":4696,"children":4697},{"style":1612},[4698],{"type":46,"value":1931},{"type":41,"tag":1600,"props":4700,"children":4701},{"style":1733},[4702],{"type":46,"value":2081},{"type":41,"tag":1600,"props":4704,"children":4705},{"style":1612},[4706],{"type":46,"value":2086},{"type":41,"tag":1600,"props":4708,"children":4709},{"class":1602,"line":1829},[4710,4714,4719,4723,4727,4731,4735,4740,4744,4749,4753,4757,4761,4766,4770],{"type":41,"tag":1600,"props":4711,"children":4712},{"style":1733},[4713],{"type":46,"value":2095},{"type":41,"tag":1600,"props":4715,"children":4716},{"style":1618},[4717],{"type":46,"value":4718}," appointment",{"type":41,"tag":1600,"props":4720,"children":4721},{"style":1612},[4722],{"type":46,"value":2105},{"type":41,"tag":1600,"props":4724,"children":4725},{"style":1607},[4726],{"type":46,"value":2110},{"type":41,"tag":1600,"props":4728,"children":4729},{"style":1618},[4730],{"type":46,"value":2115},{"type":41,"tag":1600,"props":4732,"children":4733},{"style":1612},[4734],{"type":46,"value":230},{"type":41,"tag":1600,"props":4736,"children":4737},{"style":1618},[4738],{"type":46,"value":4739},"db",{"type":41,"tag":1600,"props":4741,"children":4742},{"style":1612},[4743],{"type":46,"value":230},{"type":41,"tag":1600,"props":4745,"children":4746},{"style":1749},[4747],{"type":46,"value":4748},"get",{"type":41,"tag":1600,"props":4750,"children":4751},{"style":1768},[4752],{"type":46,"value":1756},{"type":41,"tag":1600,"props":4754,"children":4755},{"style":1618},[4756],{"type":46,"value":2315},{"type":41,"tag":1600,"props":4758,"children":4759},{"style":1612},[4760],{"type":46,"value":230},{"type":41,"tag":1600,"props":4762,"children":4763},{"style":1618},[4764],{"type":46,"value":4765},"appointmentId",{"type":41,"tag":1600,"props":4767,"children":4768},{"style":1768},[4769],{"type":46,"value":1931},{"type":41,"tag":1600,"props":4771,"children":4772},{"style":1612},[4773],{"type":46,"value":1652},{"type":41,"tag":1600,"props":4775,"children":4776},{"class":1602,"line":1860},[4777,4782,4786,4790,4794,4798,4802,4806,4810],{"type":41,"tag":1600,"props":4778,"children":4779},{"style":1607},[4780],{"type":46,"value":4781},"    await",{"type":41,"tag":1600,"props":4783,"children":4784},{"style":1749},[4785],{"type":46,"value":4089},{"type":41,"tag":1600,"props":4787,"children":4788},{"style":1768},[4789],{"type":46,"value":1756},{"type":41,"tag":1600,"props":4791,"children":4792},{"style":1618},[4793],{"type":46,"value":2063},{"type":41,"tag":1600,"props":4795,"children":4796},{"style":1612},[4797],{"type":46,"value":1673},{"type":41,"tag":1600,"props":4799,"children":4800},{"style":1618},[4801],{"type":46,"value":4718},{"type":41,"tag":1600,"props":4803,"children":4804},{"style":1768},[4805],{"type":46,"value":1931},{"type":41,"tag":1600,"props":4807,"children":4808},{"style":1612},[4809],{"type":46,"value":4041},{"type":41,"tag":1600,"props":4811,"children":4812},{"style":3784},[4813],{"type":46,"value":4814}," \u002F\u002F ownership check on the DOCUMENT, not just \"is someone logged in\"\n",{"type":41,"tag":1600,"props":4816,"children":4817},{"class":1602,"line":1886},[4818,4822,4826,4830,4834,4838,4843,4847,4851,4855,4859,4863,4867,4872,4876,4880,4885,4889,4893,4897],{"type":41,"tag":1600,"props":4819,"children":4820},{"style":1607},[4821],{"type":46,"value":4781},{"type":41,"tag":1600,"props":4823,"children":4824},{"style":1618},[4825],{"type":46,"value":2115},{"type":41,"tag":1600,"props":4827,"children":4828},{"style":1612},[4829],{"type":46,"value":230},{"type":41,"tag":1600,"props":4831,"children":4832},{"style":1618},[4833],{"type":46,"value":4739},{"type":41,"tag":1600,"props":4835,"children":4836},{"style":1612},[4837],{"type":46,"value":230},{"type":41,"tag":1600,"props":4839,"children":4840},{"style":1749},[4841],{"type":46,"value":4842},"patch",{"type":41,"tag":1600,"props":4844,"children":4845},{"style":1768},[4846],{"type":46,"value":1756},{"type":41,"tag":1600,"props":4848,"children":4849},{"style":1618},[4850],{"type":46,"value":2315},{"type":41,"tag":1600,"props":4852,"children":4853},{"style":1612},[4854],{"type":46,"value":230},{"type":41,"tag":1600,"props":4856,"children":4857},{"style":1618},[4858],{"type":46,"value":4765},{"type":41,"tag":1600,"props":4860,"children":4861},{"style":1612},[4862],{"type":46,"value":1673},{"type":41,"tag":1600,"props":4864,"children":4865},{"style":1612},[4866],{"type":46,"value":1615},{"type":41,"tag":1600,"props":4868,"children":4869},{"style":1768},[4870],{"type":46,"value":4871}," status",{"type":41,"tag":1600,"props":4873,"children":4874},{"style":1612},[4875],{"type":46,"value":1776},{"type":41,"tag":1600,"props":4877,"children":4878},{"style":1612},[4879],{"type":46,"value":1636},{"type":41,"tag":1600,"props":4881,"children":4882},{"style":1639},[4883],{"type":46,"value":4884},"cancelled",{"type":41,"tag":1600,"props":4886,"children":4887},{"style":1612},[4888],{"type":46,"value":1647},{"type":41,"tag":1600,"props":4890,"children":4891},{"style":1612},[4892],{"type":46,"value":1626},{"type":41,"tag":1600,"props":4894,"children":4895},{"style":1768},[4896],{"type":46,"value":1931},{"type":41,"tag":1600,"props":4898,"children":4899},{"style":1612},[4900],{"type":46,"value":1652},{"type":41,"tag":1600,"props":4902,"children":4903},{"class":1602,"line":1939},[4904,4908],{"type":41,"tag":1600,"props":4905,"children":4906},{"style":1607},[4907],{"type":46,"value":2351},{"type":41,"tag":1600,"props":4909,"children":4910},{"style":1612},[4911],{"type":46,"value":4912}," null;\n",{"type":41,"tag":1600,"props":4914,"children":4915},{"class":1602,"line":1973},[4916],{"type":41,"tag":1600,"props":4917,"children":4918},{"style":1612},[4919],{"type":46,"value":2491},{"type":41,"tag":1600,"props":4921,"children":4922},{"class":1602,"line":2007},[4923,4927,4931],{"type":41,"tag":1600,"props":4924,"children":4925},{"style":1612},[4926],{"type":46,"value":2500},{"type":41,"tag":1600,"props":4928,"children":4929},{"style":1618},[4930],{"type":46,"value":1931},{"type":41,"tag":1600,"props":4932,"children":4933},{"style":1612},[4934],{"type":46,"value":1652},{"type":41,"tag":448,"props":4936,"children":4937},{},[],{"type":46,"value":4939},"The four hard rules this codifies, every time:",{"type":41,"tag":1537,"props":4941,"children":4942},{},[4943,5020,5098,5121],{"type":41,"tag":127,"props":4944,"children":4945},{},[4946,4958,4959,4964,4965,4971,4972,4977,4978,4983,4984,4990,4992,4997,4999,5005,5007,5012,5013,5018],{"type":41,"tag":87,"props":4947,"children":4948},{},[4949,4951,4956],{"type":46,"value":4950},"Identity comes from ",{"type":41,"tag":49,"props":4952,"children":4954},{"className":4953},[],[4955],{"type":46,"value":3438},{"type":46,"value":4957},", never from an argument.",{"type":46,"value":514},{"type":41,"tag":49,"props":4960,"children":4962},{"className":4961},[],[4963],{"type":46,"value":3459},{"type":46,"value":264},{"type":41,"tag":49,"props":4966,"children":4968},{"className":4967},[],[4969],{"type":46,"value":4970},"actorId",{"type":46,"value":264},{"type":41,"tag":49,"props":4973,"children":4975},{"className":4974},[],[4976],{"type":46,"value":3473},{"type":46,"value":264},{"type":41,"tag":49,"props":4979,"children":4981},{"className":4980},[],[4982],{"type":46,"value":3466},{"type":46,"value":264},{"type":41,"tag":49,"props":4985,"children":4987},{"className":4986},[],[4988],{"type":46,"value":4989},"accountId",{"type":46,"value":4991}," field on ",{"type":41,"tag":49,"props":4993,"children":4995},{"className":4994},[],[4996],{"type":46,"value":2315},{"type":46,"value":4998}," is a standing invitation to impersonate — if the function needs to know who's calling, call ",{"type":41,"tag":49,"props":5000,"children":5002},{"className":5001},[],[5003],{"type":46,"value":5004},"requireIdentity(ctx)",{"type":46,"value":5006},", don't accept it as a parameter (an internal\u002Fadmin function that must operate on an arbitrary user is the one legitimate exception — keep it ",{"type":41,"tag":49,"props":5008,"children":5010},{"className":5009},[],[5011],{"type":46,"value":402},{"type":46,"value":264},{"type":41,"tag":49,"props":5014,"children":5016},{"className":5015},[],[5017],{"type":46,"value":395},{"type":46,"value":5019},", never public).",{"type":41,"tag":127,"props":5021,"children":5022},{},[5023,5035,5037,5042,5044,5049,5051,5056,5058,5063,5064,5069,5070,5075,5076,5081,5083,5089,5090,5096],{"type":41,"tag":87,"props":5024,"children":5025},{},[5026,5028,5033],{"type":46,"value":5027},"Every read or mutate keyed by an ",{"type":41,"tag":49,"props":5029,"children":5031},{"className":5030},[],[5032],{"type":46,"value":2417},{"type":46,"value":5034}," argument verifies ownership server-side before touching the row.",{"type":46,"value":5036}," Loading the doc and checking ",{"type":41,"tag":49,"props":5038,"children":5040},{"className":5039},[],[5041],{"type":46,"value":3438},{"type":46,"value":5043}," proves someone is logged in; it doesn't prove they own ",{"type":41,"tag":1413,"props":5045,"children":5046},{},[5047],{"type":46,"value":5048},"this",{"type":46,"value":5050}," row. ",{"type":41,"tag":49,"props":5052,"children":5054},{"className":5053},[],[5055],{"type":46,"value":3637},{"type":46,"value":5057}," (or the same comparison inlined) closes that gap for ",{"type":41,"tag":49,"props":5059,"children":5061},{"className":5060},[],[5062],{"type":46,"value":3497},{"type":46,"value":264},{"type":41,"tag":49,"props":5065,"children":5067},{"className":5066},[],[5068],{"type":46,"value":3504},{"type":46,"value":264},{"type":41,"tag":49,"props":5071,"children":5073},{"className":5072},[],[5074],{"type":46,"value":3511},{"type":46,"value":264},{"type":41,"tag":49,"props":5077,"children":5079},{"className":5078},[],[5080],{"type":46,"value":3518},{"type":46,"value":5082},"-shaped mutations and for ",{"type":41,"tag":49,"props":5084,"children":5086},{"className":5085},[],[5087],{"type":46,"value":5088},"get*",{"type":46,"value":264},{"type":41,"tag":49,"props":5091,"children":5093},{"className":5092},[],[5094],{"type":46,"value":5095},"list*",{"type":46,"value":5097},"-shaped queries alike.",{"type":41,"tag":127,"props":5099,"children":5100},{},[5101,5106,5108,5113,5114,5119],{"type":41,"tag":87,"props":5102,"children":5103},{},[5104],{"type":46,"value":5105},"Never expose a public query that returns PII or financial fields (email, revenue, order\u002Faudit history) gated only by a client-supplied id.",{"type":46,"value":5107}," Scope every such query through ",{"type":41,"tag":49,"props":5109,"children":5111},{"className":5110},[],[5112],{"type":46,"value":3630},{"type":46,"value":264},{"type":41,"tag":49,"props":5115,"children":5117},{"className":5116},[],[5118],{"type":46,"value":3637},{"type":46,"value":5120}," (or an explicit staff\u002Frole check) before it touches rows outside the caller's own scope.",{"type":41,"tag":127,"props":5122,"children":5123},{},[5124,5143,5144,5150,5151,5157,5158,5164,5166,5171,5173,5178,5179,5184],{"type":41,"tag":87,"props":5125,"children":5126},{},[5127,5129,5134,5136,5141],{"type":46,"value":5128},"A ",{"type":41,"tag":49,"props":5130,"children":5132},{"className":5131},[],[5133],{"type":46,"value":1042},{"type":46,"value":5135}," arg used as a foreign key on a write needs the ",{"type":41,"tag":1413,"props":5137,"children":5138},{},[5139],{"type":46,"value":5140},"parent's",{"type":46,"value":5142}," ownership checked, not just the caller's identity.",{"type":46,"value":682},{"type":41,"tag":49,"props":5145,"children":5147},{"className":5146},[],[5148],{"type":46,"value":5149},"createTask({ projectId })",{"type":46,"value":375},{"type":41,"tag":49,"props":5152,"children":5154},{"className":5153},[],[5155],{"type":46,"value":5156},"addCard({ boardId })",{"type":46,"value":375},{"type":41,"tag":49,"props":5159,"children":5161},{"className":5160},[],[5162],{"type":46,"value":5163},"createInvoice({ accountId })",{"type":46,"value":5165}," must load the referenced project\u002Fboard\u002Faccount and ",{"type":41,"tag":49,"props":5167,"children":5169},{"className":5168},[],[5170],{"type":46,"value":3637},{"type":46,"value":5172}," it (or verify membership) before inserting the child row — creating into someone else's container is the same defect as mutating their row, and it survives an identity-from-arg fix unless checked separately. After fixing rules 1–3, re-audit every ",{"type":41,"tag":1413,"props":5174,"children":5175},{},[5176],{"type":46,"value":5177},"remaining",{"type":46,"value":682},{"type":41,"tag":49,"props":5180,"children":5182},{"className":5181},[],[5183],{"type":46,"value":1042},{"type":46,"value":5185}," arg in every public mutation for this.",{"type":41,"tag":127,"props":5187,"children":5188},{},[5189,5191,5196,5197,5203,5204,5210,5212,5217,5219,5224],{"type":46,"value":5190},"Don't roll your own ",{"type":41,"tag":49,"props":5192,"children":5194},{"className":5193},[],[5195],{"type":46,"value":3141},{"type":46,"value":264},{"type":41,"tag":49,"props":5198,"children":5200},{"className":5199},[],[5201],{"type":46,"value":5202},"sessions",{"type":46,"value":264},{"type":41,"tag":49,"props":5205,"children":5207},{"className":5206},[],[5208],{"type":46,"value":5209},"accounts",{"type":46,"value":5211}," tables. Use Convex Auth or WorkOS plus a thin ",{"type":41,"tag":49,"props":5213,"children":5215},{"className":5214},[],[5216],{"type":46,"value":3141},{"type":46,"value":5218}," table keyed by ",{"type":41,"tag":49,"props":5220,"children":5222},{"className":5221},[],[5223],{"type":46,"value":3660},{"type":46,"value":230},{"type":41,"tag":127,"props":5226,"children":5227},{},[5228,5248,5250,5255,5257,5263,5264,5269,5271,5276,5278,5284,5286,5291,5293,5298,5299,5304,5306,5312,5313],{"type":41,"tag":87,"props":5229,"children":5230},{},[5231,5233,5239,5241,5247],{"type":46,"value":5232},"Setting up Convex Auth? ",{"type":41,"tag":49,"props":5234,"children":5236},{"className":5235},[],[5237],{"type":46,"value":5238},"convex\u002Fauth.config.ts",{"type":46,"value":5240}," is MANDATORY — emit it every time, same turn as ",{"type":41,"tag":49,"props":5242,"children":5244},{"className":5243},[],[5245],{"type":46,"value":5246},"auth.ts",{"type":46,"value":230},{"type":46,"value":5249}," It is the single most-skipped file and its absence is the worst possible failure mode: sign-up\u002Fsign-in ",{"type":41,"tag":1413,"props":5251,"children":5252},{},[5253],{"type":46,"value":5254},"succeed",{"type":46,"value":5256}," server-side and tokens get minted, but ",{"type":41,"tag":49,"props":5258,"children":5260},{"className":5259},[],[5261],{"type":46,"value":5262},"getAuthUserId(ctx)",{"type":46,"value":1152},{"type":41,"tag":49,"props":5265,"children":5267},{"className":5266},[],[5268],{"type":46,"value":3481},{"type":46,"value":5270}," return ",{"type":41,"tag":49,"props":5272,"children":5274},{"className":5273},[],[5275],{"type":46,"value":2591},{"type":46,"value":5277}," on every request because the deployment has no registered JWT issuer. The app looks permanently \"signed out\" — queries return ",{"type":41,"tag":49,"props":5279,"children":5281},{"className":5280},[],[5282],{"type":46,"value":5283},"[]",{"type":46,"value":5285},", seeds throw \"not signed in\", and ",{"type":41,"tag":87,"props":5287,"children":5288},{},[5289],{"type":46,"value":5290},"nothing errors anywhere",{"type":46,"value":5292},". Auth is not wired until this file exists next to ",{"type":41,"tag":49,"props":5294,"children":5296},{"className":5295},[],[5297],{"type":46,"value":5246},{"type":46,"value":375},{"type":41,"tag":49,"props":5300,"children":5302},{"className":5301},[],[5303],{"type":46,"value":864},{"type":46,"value":5305},", and ",{"type":41,"tag":49,"props":5307,"children":5309},{"className":5308},[],[5310],{"type":46,"value":5311},"authTables",{"type":46,"value":1776},{"type":41,"tag":1589,"props":5314,"children":5316},{"className":1591,"code":5315,"language":1593,"meta":1594,"style":1594},"\u002F\u002F convex\u002Fauth.config.ts\nexport default {\n  providers: [{ domain: process.env.CONVEX_SITE_URL, applicationID: \"convex\" }],\n};\n",[5317],{"type":41,"tag":49,"props":5318,"children":5319},{"__ignoreMap":1594},[5320,5328,5344,5434],{"type":41,"tag":1600,"props":5321,"children":5322},{"class":1602,"line":1603},[5323],{"type":41,"tag":1600,"props":5324,"children":5325},{"style":3784},[5326],{"type":46,"value":5327},"\u002F\u002F convex\u002Fauth.config.ts\n",{"type":41,"tag":1600,"props":5329,"children":5330},{"class":1602,"line":24},[5331,5335,5340],{"type":41,"tag":1600,"props":5332,"children":5333},{"style":1607},[5334],{"type":46,"value":1730},{"type":41,"tag":1600,"props":5336,"children":5337},{"style":1607},[5338],{"type":46,"value":5339}," default",{"type":41,"tag":1600,"props":5341,"children":5342},{"style":1612},[5343],{"type":46,"value":2086},{"type":41,"tag":1600,"props":5345,"children":5346},{"class":1602,"line":1714},[5347,5352,5356,5360,5364,5369,5373,5378,5382,5387,5391,5396,5400,5405,5409,5413,5417,5421,5425,5430],{"type":41,"tag":1600,"props":5348,"children":5349},{"style":1768},[5350],{"type":46,"value":5351},"  providers",{"type":41,"tag":1600,"props":5353,"children":5354},{"style":1612},[5355],{"type":46,"value":1776},{"type":41,"tag":1600,"props":5357,"children":5358},{"style":1618},[5359],{"type":46,"value":2843},{"type":41,"tag":1600,"props":5361,"children":5362},{"style":1612},[5363],{"type":46,"value":2394},{"type":41,"tag":1600,"props":5365,"children":5366},{"style":1768},[5367],{"type":46,"value":5368}," domain",{"type":41,"tag":1600,"props":5370,"children":5371},{"style":1612},[5372],{"type":46,"value":1776},{"type":41,"tag":1600,"props":5374,"children":5375},{"style":1618},[5376],{"type":46,"value":5377}," process",{"type":41,"tag":1600,"props":5379,"children":5380},{"style":1612},[5381],{"type":46,"value":230},{"type":41,"tag":1600,"props":5383,"children":5384},{"style":1618},[5385],{"type":46,"value":5386},"env",{"type":41,"tag":1600,"props":5388,"children":5389},{"style":1612},[5390],{"type":46,"value":230},{"type":41,"tag":1600,"props":5392,"children":5393},{"style":1618},[5394],{"type":46,"value":5395},"CONVEX_SITE_URL",{"type":41,"tag":1600,"props":5397,"children":5398},{"style":1612},[5399],{"type":46,"value":1673},{"type":41,"tag":1600,"props":5401,"children":5402},{"style":1768},[5403],{"type":46,"value":5404}," applicationID",{"type":41,"tag":1600,"props":5406,"children":5407},{"style":1612},[5408],{"type":46,"value":1776},{"type":41,"tag":1600,"props":5410,"children":5411},{"style":1612},[5412],{"type":46,"value":1636},{"type":41,"tag":1600,"props":5414,"children":5415},{"style":1639},[5416],{"type":46,"value":8},{"type":41,"tag":1600,"props":5418,"children":5419},{"style":1612},[5420],{"type":46,"value":1647},{"type":41,"tag":1600,"props":5422,"children":5423},{"style":1612},[5424],{"type":46,"value":1626},{"type":41,"tag":1600,"props":5426,"children":5427},{"style":1618},[5428],{"type":46,"value":5429},"]",{"type":41,"tag":1600,"props":5431,"children":5432},{"style":1612},[5433],{"type":46,"value":1936},{"type":41,"tag":1600,"props":5435,"children":5436},{"class":1602,"line":1724},[5437],{"type":41,"tag":1600,"props":5438,"children":5439},{"style":1612},[5440],{"type":46,"value":5441},"};\n",{"type":41,"tag":127,"props":5443,"children":5444},{},[5445,5472,5474,5479,5481,5487,5489,5495,5497,5503,5505,5511,5513,5519],{"type":41,"tag":87,"props":5446,"children":5447},{},[5448,5450,5456,5457,5463,5464,5470],{"type":46,"value":5449},"Convex Auth needs ",{"type":41,"tag":49,"props":5451,"children":5453},{"className":5452},[],[5454],{"type":46,"value":5455},"JWT_PRIVATE_KEY",{"type":46,"value":1152},{"type":41,"tag":49,"props":5458,"children":5460},{"className":5459},[],[5461],{"type":46,"value":5462},"JWKS",{"type":46,"value":1152},{"type":41,"tag":49,"props":5465,"children":5467},{"className":5466},[],[5468],{"type":46,"value":5469},"SITE_URL",{"type":46,"value":5471}," set on the deployment",{"type":46,"value":5473}," — and these are ",{"type":41,"tag":87,"props":5475,"children":5476},{},[5477],{"type":46,"value":5478},"per-deployment: they do NOT carry from dev to prod.",{"type":46,"value":5480}," Set them again on prod with\u002Fbefore the first prod deploy. Symptom of missing keys: sign-in throws ",{"type":41,"tag":49,"props":5482,"children":5484},{"className":5483},[],[5485],{"type":46,"value":5486},"TypeError: Cannot read properties of null (reading 'redirect')",{"type":46,"value":5488},". Generate\u002Fset via ",{"type":41,"tag":49,"props":5490,"children":5492},{"className":5491},[],[5493],{"type":46,"value":5494},"npx @convex-dev\u002Fauth --skip-git-check --web-server-url \u003Curl>",{"type":46,"value":5496},". When setting a multi-line PEM by hand, pass it as ",{"type":41,"tag":49,"props":5498,"children":5500},{"className":5499},[],[5501],{"type":46,"value":5502},"\"$(cat key.pem)\"",{"type":46,"value":5504}," — ",{"type":41,"tag":49,"props":5506,"children":5508},{"className":5507},[],[5509],{"type":46,"value":5510},"npx convex env set --prod JWT_PRIVATE_KEY \"\u003Cpasted-pem>\"",{"type":46,"value":5512}," silently mangles the newlines and the var ends up unset (no error; only ",{"type":41,"tag":49,"props":5514,"children":5516},{"className":5515},[],[5517],{"type":46,"value":5518},"env list",{"type":46,"value":5520}," reveals it).",{"type":41,"tag":1582,"props":5522,"children":5524},{"id":5523},"file-storage",[5525],{"type":46,"value":5526},"File storage",{"type":41,"tag":123,"props":5528,"children":5529},{},[5530,5549],{"type":41,"tag":127,"props":5531,"children":5532},{},[5533,5535,5541,5543,5547],{"type":46,"value":5534},"Store the ",{"type":41,"tag":49,"props":5536,"children":5538},{"className":5537},[],[5539],{"type":46,"value":5540},"Id\u003C\"_storage\">",{"type":46,"value":5542}," in tables, ",{"type":41,"tag":87,"props":5544,"children":5545},{},[5546],{"type":46,"value":3674},{"type":46,"value":5548}," the URL. URLs expire.",{"type":41,"tag":127,"props":5550,"children":5551},{},[5552,5554,5560],{"type":46,"value":5553},"Fetch the URL on read: ",{"type":41,"tag":49,"props":5555,"children":5557},{"className":5556},[],[5558],{"type":46,"value":5559},"await ctx.storage.getUrl(storageId)",{"type":46,"value":230},{"type":41,"tag":111,"props":5562,"children":5564},{"id":5563},"component-first-reflexes",[5565],{"type":46,"value":5566},"Component-first reflexes",{"type":41,"tag":42,"props":5568,"children":5569},{},[5570,5572,5580],{"type":46,"value":5571},"Before writing custom code, check ",{"type":41,"tag":5573,"props":5574,"children":5578},"a",{"href":5575,"rel":5576},"https:\u002F\u002Fwww.convex.dev\u002Fcomponents",[5577],"nofollow",[5579],{"type":46,"value":5575},{"type":46,"value":5581},". Reach for these without thinking:",{"type":41,"tag":1582,"props":5583,"children":5585},{"id":5584},"agentic-apps-threads-tools-rag-convex-devagent",[5586,5588],{"type":46,"value":5587},"Agentic apps (threads \u002F tools \u002F RAG) → ",{"type":41,"tag":49,"props":5589,"children":5591},{"className":5590},[],[5592],{"type":46,"value":70},{"type":41,"tag":42,"props":5594,"children":5595},{},[5596,5598,5603],{"type":46,"value":5597},"When an app needs multiple threads, tool-calls, RAG, or durable multi-turn history, reach for ",{"type":41,"tag":49,"props":5599,"children":5601},{"className":5600},[],[5602],{"type":46,"value":70},{"type":46,"value":5604}," instead of hand-rolling it.",{"type":41,"tag":42,"props":5606,"children":5607},{},[5608,5610,5615,5617,5622],{"type":46,"value":5609},"For a ",{"type":41,"tag":87,"props":5611,"children":5612},{},[5613],{"type":46,"value":5614},"simple streaming chat",{"type":46,"value":5616}," (one thread, token-by-token), build it directly: a ",{"type":41,"tag":49,"props":5618,"children":5620},{"className":5619},[],[5621],{"type":46,"value":62},{"type":46,"value":5623}," table plus an action that streams tokens in, read by a reactive query. That is currently more reliable than the component's streaming wiring.",{"type":41,"tag":1589,"props":5625,"children":5627},{"className":1591,"code":5626,"language":1593,"meta":1594,"style":1594},"\u002F\u002F convex\u002Fconvex.config.ts\nimport { defineApp } from \"convex\u002Fserver\";\nimport agent from \"@convex-dev\u002Fagent\u002Fconvex.config\";\nconst app = defineApp();\napp.use(agent);\nexport default app;\n\n\u002F\u002F convex\u002Fchat.ts\nimport { Agent } from \"@convex-dev\u002Fagent\";\nimport { anthropic } from \"@ai-sdk\u002Fanthropic\";\nimport { components } from \".\u002F_generated\u002Fapi\";\n\nexport const myAgent = new Agent(components.agent, {\n  chat: anthropic(\"claude-opus-4-7\"),\n  instructions: \"…\",\n});\n",[5628],{"type":41,"tag":49,"props":5629,"children":5630},{"__ignoreMap":1594},[5631,5639,5679,5712,5741,5767,5787,5794,5802,5842,5883,5924,5931,5981,6022,6051],{"type":41,"tag":1600,"props":5632,"children":5633},{"class":1602,"line":1603},[5634],{"type":41,"tag":1600,"props":5635,"children":5636},{"style":3784},[5637],{"type":46,"value":5638},"\u002F\u002F convex\u002Fconvex.config.ts\n",{"type":41,"tag":1600,"props":5640,"children":5641},{"class":1602,"line":24},[5642,5646,5650,5655,5659,5663,5667,5671,5675],{"type":41,"tag":1600,"props":5643,"children":5644},{"style":1607},[5645],{"type":46,"value":739},{"type":41,"tag":1600,"props":5647,"children":5648},{"style":1612},[5649],{"type":46,"value":1615},{"type":41,"tag":1600,"props":5651,"children":5652},{"style":1618},[5653],{"type":46,"value":5654}," defineApp",{"type":41,"tag":1600,"props":5656,"children":5657},{"style":1612},[5658],{"type":46,"value":1626},{"type":41,"tag":1600,"props":5660,"children":5661},{"style":1607},[5662],{"type":46,"value":1631},{"type":41,"tag":1600,"props":5664,"children":5665},{"style":1612},[5666],{"type":46,"value":1636},{"type":41,"tag":1600,"props":5668,"children":5669},{"style":1639},[5670],{"type":46,"value":472},{"type":41,"tag":1600,"props":5672,"children":5673},{"style":1612},[5674],{"type":46,"value":1647},{"type":41,"tag":1600,"props":5676,"children":5677},{"style":1612},[5678],{"type":46,"value":1652},{"type":41,"tag":1600,"props":5680,"children":5681},{"class":1602,"line":1714},[5682,5686,5691,5695,5699,5704,5708],{"type":41,"tag":1600,"props":5683,"children":5684},{"style":1607},[5685],{"type":46,"value":739},{"type":41,"tag":1600,"props":5687,"children":5688},{"style":1618},[5689],{"type":46,"value":5690}," agent ",{"type":41,"tag":1600,"props":5692,"children":5693},{"style":1607},[5694],{"type":46,"value":1457},{"type":41,"tag":1600,"props":5696,"children":5697},{"style":1612},[5698],{"type":46,"value":1636},{"type":41,"tag":1600,"props":5700,"children":5701},{"style":1639},[5702],{"type":46,"value":5703},"@convex-dev\u002Fagent\u002Fconvex.config",{"type":41,"tag":1600,"props":5705,"children":5706},{"style":1612},[5707],{"type":46,"value":1647},{"type":41,"tag":1600,"props":5709,"children":5710},{"style":1612},[5711],{"type":46,"value":1652},{"type":41,"tag":1600,"props":5713,"children":5714},{"class":1602,"line":1724},[5715,5720,5725,5729,5733,5737],{"type":41,"tag":1600,"props":5716,"children":5717},{"style":1733},[5718],{"type":46,"value":5719},"const",{"type":41,"tag":1600,"props":5721,"children":5722},{"style":1618},[5723],{"type":46,"value":5724}," app ",{"type":41,"tag":1600,"props":5726,"children":5727},{"style":1612},[5728],{"type":46,"value":1746},{"type":41,"tag":1600,"props":5730,"children":5731},{"style":1749},[5732],{"type":46,"value":5654},{"type":41,"tag":1600,"props":5734,"children":5735},{"style":1618},[5736],{"type":46,"value":1966},{"type":41,"tag":1600,"props":5738,"children":5739},{"style":1612},[5740],{"type":46,"value":1652},{"type":41,"tag":1600,"props":5742,"children":5743},{"class":1602,"line":1764},[5744,5749,5753,5758,5763],{"type":41,"tag":1600,"props":5745,"children":5746},{"style":1618},[5747],{"type":46,"value":5748},"app",{"type":41,"tag":1600,"props":5750,"children":5751},{"style":1612},[5752],{"type":46,"value":230},{"type":41,"tag":1600,"props":5754,"children":5755},{"style":1749},[5756],{"type":46,"value":5757},"use",{"type":41,"tag":1600,"props":5759,"children":5760},{"style":1618},[5761],{"type":46,"value":5762},"(agent)",{"type":41,"tag":1600,"props":5764,"children":5765},{"style":1612},[5766],{"type":46,"value":1652},{"type":41,"tag":1600,"props":5768,"children":5769},{"class":1602,"line":1829},[5770,5774,5778,5783],{"type":41,"tag":1600,"props":5771,"children":5772},{"style":1607},[5773],{"type":46,"value":1730},{"type":41,"tag":1600,"props":5775,"children":5776},{"style":1607},[5777],{"type":46,"value":5339},{"type":41,"tag":1600,"props":5779,"children":5780},{"style":1618},[5781],{"type":46,"value":5782}," app",{"type":41,"tag":1600,"props":5784,"children":5785},{"style":1612},[5786],{"type":46,"value":1652},{"type":41,"tag":1600,"props":5788,"children":5789},{"class":1602,"line":1860},[5790],{"type":41,"tag":1600,"props":5791,"children":5792},{"emptyLinePlaceholder":1718},[5793],{"type":46,"value":1721},{"type":41,"tag":1600,"props":5795,"children":5796},{"class":1602,"line":1886},[5797],{"type":41,"tag":1600,"props":5798,"children":5799},{"style":3784},[5800],{"type":46,"value":5801},"\u002F\u002F convex\u002Fchat.ts\n",{"type":41,"tag":1600,"props":5803,"children":5804},{"class":1602,"line":1939},[5805,5809,5813,5818,5822,5826,5830,5834,5838],{"type":41,"tag":1600,"props":5806,"children":5807},{"style":1607},[5808],{"type":46,"value":739},{"type":41,"tag":1600,"props":5810,"children":5811},{"style":1612},[5812],{"type":46,"value":1615},{"type":41,"tag":1600,"props":5814,"children":5815},{"style":1618},[5816],{"type":46,"value":5817}," Agent",{"type":41,"tag":1600,"props":5819,"children":5820},{"style":1612},[5821],{"type":46,"value":1626},{"type":41,"tag":1600,"props":5823,"children":5824},{"style":1607},[5825],{"type":46,"value":1631},{"type":41,"tag":1600,"props":5827,"children":5828},{"style":1612},[5829],{"type":46,"value":1636},{"type":41,"tag":1600,"props":5831,"children":5832},{"style":1639},[5833],{"type":46,"value":70},{"type":41,"tag":1600,"props":5835,"children":5836},{"style":1612},[5837],{"type":46,"value":1647},{"type":41,"tag":1600,"props":5839,"children":5840},{"style":1612},[5841],{"type":46,"value":1652},{"type":41,"tag":1600,"props":5843,"children":5844},{"class":1602,"line":1973},[5845,5849,5853,5858,5862,5866,5870,5875,5879],{"type":41,"tag":1600,"props":5846,"children":5847},{"style":1607},[5848],{"type":46,"value":739},{"type":41,"tag":1600,"props":5850,"children":5851},{"style":1612},[5852],{"type":46,"value":1615},{"type":41,"tag":1600,"props":5854,"children":5855},{"style":1618},[5856],{"type":46,"value":5857}," anthropic",{"type":41,"tag":1600,"props":5859,"children":5860},{"style":1612},[5861],{"type":46,"value":1626},{"type":41,"tag":1600,"props":5863,"children":5864},{"style":1607},[5865],{"type":46,"value":1631},{"type":41,"tag":1600,"props":5867,"children":5868},{"style":1612},[5869],{"type":46,"value":1636},{"type":41,"tag":1600,"props":5871,"children":5872},{"style":1639},[5873],{"type":46,"value":5874},"@ai-sdk\u002Fanthropic",{"type":41,"tag":1600,"props":5876,"children":5877},{"style":1612},[5878],{"type":46,"value":1647},{"type":41,"tag":1600,"props":5880,"children":5881},{"style":1612},[5882],{"type":46,"value":1652},{"type":41,"tag":1600,"props":5884,"children":5885},{"class":1602,"line":2007},[5886,5890,5894,5899,5903,5907,5911,5916,5920],{"type":41,"tag":1600,"props":5887,"children":5888},{"style":1607},[5889],{"type":46,"value":739},{"type":41,"tag":1600,"props":5891,"children":5892},{"style":1612},[5893],{"type":46,"value":1615},{"type":41,"tag":1600,"props":5895,"children":5896},{"style":1618},[5897],{"type":46,"value":5898}," components",{"type":41,"tag":1600,"props":5900,"children":5901},{"style":1612},[5902],{"type":46,"value":1626},{"type":41,"tag":1600,"props":5904,"children":5905},{"style":1607},[5906],{"type":46,"value":1631},{"type":41,"tag":1600,"props":5908,"children":5909},{"style":1612},[5910],{"type":46,"value":1636},{"type":41,"tag":1600,"props":5912,"children":5913},{"style":1639},[5914],{"type":46,"value":5915},".\u002F_generated\u002Fapi",{"type":41,"tag":1600,"props":5917,"children":5918},{"style":1612},[5919],{"type":46,"value":1647},{"type":41,"tag":1600,"props":5921,"children":5922},{"style":1612},[5923],{"type":46,"value":1652},{"type":41,"tag":1600,"props":5925,"children":5926},{"class":1602,"line":2024},[5927],{"type":41,"tag":1600,"props":5928,"children":5929},{"emptyLinePlaceholder":1718},[5930],{"type":46,"value":1721},{"type":41,"tag":1600,"props":5932,"children":5933},{"class":1602,"line":2037},[5934,5938,5942,5947,5951,5955,5959,5964,5968,5973,5977],{"type":41,"tag":1600,"props":5935,"children":5936},{"style":1607},[5937],{"type":46,"value":1730},{"type":41,"tag":1600,"props":5939,"children":5940},{"style":1733},[5941],{"type":46,"value":1736},{"type":41,"tag":1600,"props":5943,"children":5944},{"style":1618},[5945],{"type":46,"value":5946}," myAgent ",{"type":41,"tag":1600,"props":5948,"children":5949},{"style":1612},[5950],{"type":46,"value":1746},{"type":41,"tag":1600,"props":5952,"children":5953},{"style":1612},[5954],{"type":46,"value":3994},{"type":41,"tag":1600,"props":5956,"children":5957},{"style":1749},[5958],{"type":46,"value":5817},{"type":41,"tag":1600,"props":5960,"children":5961},{"style":1618},[5962],{"type":46,"value":5963},"(components",{"type":41,"tag":1600,"props":5965,"children":5966},{"style":1612},[5967],{"type":46,"value":230},{"type":41,"tag":1600,"props":5969,"children":5970},{"style":1618},[5971],{"type":46,"value":5972},"agent",{"type":41,"tag":1600,"props":5974,"children":5975},{"style":1612},[5976],{"type":46,"value":1673},{"type":41,"tag":1600,"props":5978,"children":5979},{"style":1612},[5980],{"type":46,"value":2086},{"type":41,"tag":1600,"props":5982,"children":5983},{"class":1602,"line":2089},[5984,5989,5993,5997,6001,6005,6010,6014,6018],{"type":41,"tag":1600,"props":5985,"children":5986},{"style":1768},[5987],{"type":46,"value":5988},"  chat",{"type":41,"tag":1600,"props":5990,"children":5991},{"style":1612},[5992],{"type":46,"value":1776},{"type":41,"tag":1600,"props":5994,"children":5995},{"style":1749},[5996],{"type":46,"value":5857},{"type":41,"tag":1600,"props":5998,"children":5999},{"style":1618},[6000],{"type":46,"value":1756},{"type":41,"tag":1600,"props":6002,"children":6003},{"style":1612},[6004],{"type":46,"value":1647},{"type":41,"tag":1600,"props":6006,"children":6007},{"style":1639},[6008],{"type":46,"value":6009},"claude-opus-4-7",{"type":41,"tag":1600,"props":6011,"children":6012},{"style":1612},[6013],{"type":46,"value":1647},{"type":41,"tag":1600,"props":6015,"children":6016},{"style":1618},[6017],{"type":46,"value":1931},{"type":41,"tag":1600,"props":6019,"children":6020},{"style":1612},[6021],{"type":46,"value":1936},{"type":41,"tag":1600,"props":6023,"children":6024},{"class":1602,"line":2127},[6025,6030,6034,6038,6043,6047],{"type":41,"tag":1600,"props":6026,"children":6027},{"style":1768},[6028],{"type":46,"value":6029},"  instructions",{"type":41,"tag":1600,"props":6031,"children":6032},{"style":1612},[6033],{"type":46,"value":1776},{"type":41,"tag":1600,"props":6035,"children":6036},{"style":1612},[6037],{"type":46,"value":1636},{"type":41,"tag":1600,"props":6039,"children":6040},{"style":1639},[6041],{"type":46,"value":6042},"…",{"type":41,"tag":1600,"props":6044,"children":6045},{"style":1612},[6046],{"type":46,"value":1647},{"type":41,"tag":1600,"props":6048,"children":6049},{"style":1612},[6050],{"type":46,"value":1936},{"type":41,"tag":1600,"props":6052,"children":6053},{"class":1602,"line":2161},[6054,6058,6062],{"type":41,"tag":1600,"props":6055,"children":6056},{"style":1612},[6057],{"type":46,"value":2500},{"type":41,"tag":1600,"props":6059,"children":6060},{"style":1618},[6061],{"type":46,"value":1931},{"type":41,"tag":1600,"props":6063,"children":6064},{"style":1612},[6065],{"type":46,"value":1652},{"type":41,"tag":1582,"props":6067,"children":6069},{"id":6068},"long-running-multi-step-convex-devworkflow",[6070,6072],{"type":46,"value":6071},"Long-running \u002F multi-step → ",{"type":41,"tag":49,"props":6073,"children":6075},{"className":6074},[],[6076],{"type":46,"value":6077},"@convex-dev\u002Fworkflow",{"type":41,"tag":42,"props":6079,"children":6080},{},[6081],{"type":46,"value":6082},"Anything crossing the function-time limit, needing retries on partial failure, or resumability across crashes.",{"type":41,"tag":1582,"props":6084,"children":6086},{"id":6085},"other-defaults",[6087],{"type":46,"value":6088},"Other defaults",{"type":41,"tag":335,"props":6090,"children":6091},{},[6092,6108],{"type":41,"tag":339,"props":6093,"children":6094},{},[6095],{"type":41,"tag":343,"props":6096,"children":6097},{},[6098,6103],{"type":41,"tag":347,"props":6099,"children":6100},{},[6101],{"type":46,"value":6102},"Need",{"type":41,"tag":347,"props":6104,"children":6105},{},[6106],{"type":46,"value":6107},"Component",{"type":41,"tag":358,"props":6109,"children":6110},{},[6111,6128,6145,6162,6179,6195,6212,6229,6246,6263],{"type":41,"tag":343,"props":6112,"children":6113},{},[6114,6119],{"type":41,"tag":365,"props":6115,"children":6116},{},[6117],{"type":46,"value":6118},"RAG",{"type":41,"tag":365,"props":6120,"children":6121},{},[6122],{"type":41,"tag":49,"props":6123,"children":6125},{"className":6124},[],[6126],{"type":46,"value":6127},"@convex-dev\u002Frag",{"type":41,"tag":343,"props":6129,"children":6130},{},[6131,6136],{"type":41,"tag":365,"props":6132,"children":6133},{},[6134],{"type":46,"value":6135},"Programmatic crons",{"type":41,"tag":365,"props":6137,"children":6138},{},[6139],{"type":41,"tag":49,"props":6140,"children":6142},{"className":6141},[],[6143],{"type":46,"value":6144},"@convex-dev\u002Fcrons",{"type":41,"tag":343,"props":6146,"children":6147},{},[6148,6153],{"type":41,"tag":365,"props":6149,"children":6150},{},[6151],{"type":46,"value":6152},"Schema \u002F data migrations",{"type":41,"tag":365,"props":6154,"children":6155},{},[6156],{"type":41,"tag":49,"props":6157,"children":6159},{"className":6158},[],[6160],{"type":46,"value":6161},"@convex-dev\u002Fmigrations",{"type":41,"tag":343,"props":6163,"children":6164},{},[6165,6170],{"type":41,"tag":365,"props":6166,"children":6167},{},[6168],{"type":46,"value":6169},"Rate limiting",{"type":41,"tag":365,"props":6171,"children":6172},{},[6173],{"type":41,"tag":49,"props":6174,"children":6176},{"className":6175},[],[6177],{"type":46,"value":6178},"@convex-dev\u002Frate-limiter",{"type":41,"tag":343,"props":6180,"children":6181},{},[6182,6187],{"type":41,"tag":365,"props":6183,"children":6184},{},[6185],{"type":46,"value":6186},"Counts \u002F sums",{"type":41,"tag":365,"props":6188,"children":6189},{},[6190],{"type":41,"tag":49,"props":6191,"children":6193},{"className":6192},[],[6194],{"type":46,"value":3395},{"type":41,"tag":343,"props":6196,"children":6197},{},[6198,6203],{"type":41,"tag":365,"props":6199,"children":6200},{},[6201],{"type":46,"value":6202},"High-throughput counters",{"type":41,"tag":365,"props":6204,"children":6205},{},[6206],{"type":41,"tag":49,"props":6207,"children":6209},{"className":6208},[],[6210],{"type":46,"value":6211},"@convex-dev\u002Fsharded-counter",{"type":41,"tag":343,"props":6213,"children":6214},{},[6215,6220],{"type":41,"tag":365,"props":6216,"children":6217},{},[6218],{"type":46,"value":6219},"Function-result caching",{"type":41,"tag":365,"props":6221,"children":6222},{},[6223],{"type":41,"tag":49,"props":6224,"children":6226},{"className":6225},[],[6227],{"type":46,"value":6228},"@convex-dev\u002Fcache",{"type":41,"tag":343,"props":6230,"children":6231},{},[6232,6237],{"type":41,"tag":365,"props":6233,"children":6234},{},[6235],{"type":46,"value":6236},"Online-user presence",{"type":41,"tag":365,"props":6238,"children":6239},{},[6240],{"type":41,"tag":49,"props":6241,"children":6243},{"className":6242},[],[6244],{"type":46,"value":6245},"@convex-dev\u002Fpresence",{"type":41,"tag":343,"props":6247,"children":6248},{},[6249,6254],{"type":41,"tag":365,"props":6250,"children":6251},{},[6252],{"type":46,"value":6253},"Durable LLM streaming",{"type":41,"tag":365,"props":6255,"children":6256},{},[6257],{"type":41,"tag":49,"props":6258,"children":6260},{"className":6259},[],[6261],{"type":46,"value":6262},"@convex-dev\u002Fpersistent-text-streaming",{"type":41,"tag":343,"props":6264,"children":6265},{},[6266,6271],{"type":41,"tag":365,"props":6267,"children":6268},{},[6269],{"type":46,"value":6270},"Bounded concurrency",{"type":41,"tag":365,"props":6272,"children":6273},{},[6274],{"type":41,"tag":49,"props":6275,"children":6277},{"className":6276},[],[6278],{"type":46,"value":3314},{"type":41,"tag":42,"props":6280,"children":6281},{},[6282,6284,6289,6291,6297],{"type":46,"value":6283},"External APIs (emails, payments, LLM calls) belong in ",{"type":41,"tag":49,"props":6285,"children":6287},{"className":6286},[],[6288],{"type":46,"value":388},{"type":46,"value":6290},"s. Persist via ",{"type":41,"tag":49,"props":6292,"children":6294},{"className":6293},[],[6295],{"type":46,"value":6296},"ctx.runMutation(internal.x.y, ...)",{"type":46,"value":230},{"type":41,"tag":1582,"props":6299,"children":6301},{"id":6300},"dont-add-a-parallel-service",[6302],{"type":46,"value":6303},"Don't add a parallel service",{"type":41,"tag":42,"props":6305,"children":6306},{},[6307],{"type":46,"value":6308},"Convex is the backend. Before reaching for any of these, stop:",{"type":41,"tag":123,"props":6310,"children":6311},{},[6312,6317,6329,6334,6358,6370],{"type":41,"tag":127,"props":6313,"children":6314},{},[6315],{"type":46,"value":6316},"❌ Adding a separate database or in-memory cache. Convex queries are already reactive and cached.",{"type":41,"tag":127,"props":6318,"children":6319},{},[6320,6322,6327],{"type":46,"value":6321},"❌ Adding a real-time service (WebSocket gateway, pub\u002Fsub). ",{"type":41,"tag":49,"props":6323,"children":6325},{"className":6324},[],[6326],{"type":46,"value":2667},{"type":46,"value":6328}," is reactive over WebSockets.",{"type":41,"tag":127,"props":6330,"children":6331},{},[6332],{"type":46,"value":6333},"❌ Adding a separate API server. Queries\u002Fmutations\u002Factions ARE the server.",{"type":41,"tag":127,"props":6335,"children":6336},{},[6337,6339,6344,6345,6351,6352,6357],{"type":46,"value":6338},"❌ Adding a job queue or workflow service. Use ",{"type":41,"tag":49,"props":6340,"children":6342},{"className":6341},[],[6343],{"type":46,"value":3306},{"type":46,"value":3292},{"type":41,"tag":49,"props":6346,"children":6348},{"className":6347},[],[6349],{"type":46,"value":6350},"crons.ts",{"type":46,"value":3292},{"type":41,"tag":49,"props":6353,"children":6355},{"className":6354},[],[6356],{"type":46,"value":6077},{"type":46,"value":230},{"type":41,"tag":127,"props":6359,"children":6360},{},[6361,6363,6369],{"type":46,"value":6362},"❌ Adding an object store. Use ",{"type":41,"tag":49,"props":6364,"children":6366},{"className":6365},[],[6367],{"type":46,"value":6368},"ctx.storage",{"type":46,"value":230},{"type":41,"tag":127,"props":6371,"children":6372},{},[6373,6375,6381,6382,6388],{"type":46,"value":6374},"❌ Adding a vector or text search service. Use ",{"type":41,"tag":49,"props":6376,"children":6378},{"className":6377},[],[6379],{"type":46,"value":6380},"defineTable(...).vectorIndex(...)",{"type":46,"value":1152},{"type":41,"tag":49,"props":6383,"children":6385},{"className":6384},[],[6386],{"type":46,"value":6387},".searchIndex(...)",{"type":46,"value":230},{"type":41,"tag":111,"props":6390,"children":6392},{"id":6391},"convex-helpers-dont-hand-roll-these",[6393,6399],{"type":41,"tag":49,"props":6394,"children":6396},{"className":6395},[],[6397],{"type":46,"value":6398},"convex-helpers",{"type":46,"value":6400}," — don't hand-roll these",{"type":41,"tag":42,"props":6402,"children":6403},{},[6404,6410],{"type":41,"tag":49,"props":6405,"children":6407},{"className":6406},[],[6408],{"type":46,"value":6409},"npm install convex-helpers",{"type":46,"value":6411}," before writing a custom version of any of these. It's the official utility package, not a third-party dependency:",{"type":41,"tag":335,"props":6413,"children":6414},{},[6415,6434],{"type":41,"tag":339,"props":6416,"children":6417},{},[6418],{"type":41,"tag":343,"props":6419,"children":6420},{},[6421,6425,6430],{"type":41,"tag":347,"props":6422,"children":6423},{},[6424],{"type":46,"value":6102},{"type":41,"tag":347,"props":6426,"children":6427},{},[6428],{"type":46,"value":6429},"Use",{"type":41,"tag":347,"props":6431,"children":6432},{},[6433],{"type":46,"value":356},{"type":41,"tag":358,"props":6435,"children":6436},{},[6437,6480,6522,6565,6606],{"type":41,"tag":343,"props":6438,"children":6439},{},[6440,6445,6471],{"type":41,"tag":365,"props":6441,"children":6442},{},[6443],{"type":46,"value":6444},"Auth\u002FRBAC\u002Ftenant context on every query & mutation (Convex's answer to Postgres RLS)",{"type":41,"tag":365,"props":6446,"children":6447},{},[6448,6454,6455,6461,6463,6469],{"type":41,"tag":49,"props":6449,"children":6451},{"className":6450},[],[6452],{"type":46,"value":6453},"customQuery",{"type":46,"value":1152},{"type":41,"tag":49,"props":6456,"children":6458},{"className":6457},[],[6459],{"type":46,"value":6460},"customMutation",{"type":46,"value":6462}," — wrap once, inject ",{"type":41,"tag":49,"props":6464,"children":6466},{"className":6465},[],[6467],{"type":46,"value":6468},"ctx.user",{"type":46,"value":6470}," everywhere",{"type":41,"tag":365,"props":6472,"children":6473},{},[6474],{"type":41,"tag":49,"props":6475,"children":6477},{"className":6476},[],[6478],{"type":46,"value":6479},"convex-helpers\u002Fserver\u002FcustomFunctions",{"type":41,"tag":343,"props":6481,"children":6482},{},[6483,6488,6513],{"type":41,"tag":365,"props":6484,"children":6485},{},[6486],{"type":46,"value":6487},"Follow a foreign key \u002F join",{"type":41,"tag":365,"props":6489,"children":6490},{},[6491,6497,6498,6504,6505,6511],{"type":41,"tag":49,"props":6492,"children":6494},{"className":6493},[],[6495],{"type":46,"value":6496},"getOneFrom",{"type":46,"value":375},{"type":41,"tag":49,"props":6499,"children":6501},{"className":6500},[],[6502],{"type":46,"value":6503},"getManyFrom",{"type":46,"value":375},{"type":41,"tag":49,"props":6506,"children":6508},{"className":6507},[],[6509],{"type":46,"value":6510},"getManyVia",{"type":46,"value":6512}," (many-to-many)",{"type":41,"tag":365,"props":6514,"children":6515},{},[6516],{"type":41,"tag":49,"props":6517,"children":6519},{"className":6518},[],[6520],{"type":46,"value":6521},"convex-helpers\u002Fserver\u002Frelationships",{"type":41,"tag":343,"props":6523,"children":6524},{},[6525,6530,6549],{"type":41,"tag":365,"props":6526,"children":6527},{},[6528],{"type":46,"value":6529},"Anonymous\u002Fpre-signup user tracking",{"type":41,"tag":365,"props":6531,"children":6532},{},[6533,6539,6541,6547],{"type":41,"tag":49,"props":6534,"children":6536},{"className":6535},[],[6537],{"type":46,"value":6538},"useSessionId",{"type":46,"value":6540}," (client) + ",{"type":41,"tag":49,"props":6542,"children":6544},{"className":6543},[],[6545],{"type":46,"value":6546},"SessionIdArg",{"type":46,"value":6548}," (server)",{"type":41,"tag":365,"props":6550,"children":6551},{},[6552,6558,6559],{"type":41,"tag":49,"props":6553,"children":6555},{"className":6554},[],[6556],{"type":46,"value":6557},"convex-helpers\u002Freact\u002Fsessions",{"type":46,"value":375},{"type":41,"tag":49,"props":6560,"children":6562},{"className":6561},[],[6563],{"type":46,"value":6564},"convex-helpers\u002Fserver\u002Fsessions",{"type":41,"tag":343,"props":6566,"children":6567},{},[6568,6581,6597],{"type":41,"tag":365,"props":6569,"children":6570},{},[6571,6573,6579],{"type":46,"value":6572},"Zod instead of ",{"type":41,"tag":49,"props":6574,"children":6576},{"className":6575},[],[6577],{"type":46,"value":6578},"v.*",{"type":46,"value":6580}," validators",{"type":41,"tag":365,"props":6582,"children":6583},{},[6584,6590,6591],{"type":41,"tag":49,"props":6585,"children":6587},{"className":6586},[],[6588],{"type":46,"value":6589},"zCustomQuery",{"type":46,"value":1152},{"type":41,"tag":49,"props":6592,"children":6594},{"className":6593},[],[6595],{"type":46,"value":6596},"zCustomMutation",{"type":41,"tag":365,"props":6598,"children":6599},{},[6600],{"type":41,"tag":49,"props":6601,"children":6603},{"className":6602},[],[6604],{"type":46,"value":6605},"convex-helpers\u002Fserver\u002Fzod",{"type":41,"tag":343,"props":6607,"children":6608},{},[6609,6614,6623],{"type":41,"tag":365,"props":6610,"children":6611},{},[6612],{"type":46,"value":6613},"React on data changes (fan-out notifications, computed fields)",{"type":41,"tag":365,"props":6615,"children":6616},{},[6617],{"type":41,"tag":49,"props":6618,"children":6620},{"className":6619},[],[6621],{"type":46,"value":6622},"Triggers",{"type":41,"tag":365,"props":6624,"children":6625},{},[6626],{"type":41,"tag":49,"props":6627,"children":6629},{"className":6628},[],[6630],{"type":46,"value":6631},"convex-helpers\u002Fserver\u002Ftriggers",{"type":41,"tag":42,"props":6633,"children":6634},{},[6635,6637,6642,6643,6648,6650,6656,6658,6664,6666,6671],{"type":46,"value":6636},"Prefer ",{"type":41,"tag":49,"props":6638,"children":6640},{"className":6639},[],[6641],{"type":46,"value":6453},{"type":46,"value":264},{"type":41,"tag":49,"props":6644,"children":6646},{"className":6645},[],[6647],{"type":46,"value":6460},{"type":46,"value":6649}," over a hand-rolled row-level-security helper — same idea, but type-checked at compile time instead of a runtime rule engine. Reach for the plain ",{"type":41,"tag":49,"props":6651,"children":6653},{"className":6652},[],[6654],{"type":46,"value":6655},"filter()",{"type":46,"value":6657}," helper (",{"type":41,"tag":49,"props":6659,"children":6661},{"className":6660},[],[6662],{"type":46,"value":6663},"convex-helpers\u002Fserver\u002Ffilter",{"type":46,"value":6665},") only for small result sets with logic too dynamic for an index; ",{"type":41,"tag":49,"props":6667,"children":6669},{"className":6668},[],[6670],{"type":46,"value":150},{"type":46,"value":6672}," is still the default.",{"type":41,"tag":111,"props":6674,"children":6676},{"id":6675},"runtime-errors-what-they-mean",[6677],{"type":46,"value":6678},"Runtime errors — what they mean",{"type":41,"tag":335,"props":6680,"children":6681},{},[6682,6703],{"type":41,"tag":339,"props":6683,"children":6684},{},[6685],{"type":41,"tag":343,"props":6686,"children":6687},{},[6688,6693,6698],{"type":41,"tag":347,"props":6689,"children":6690},{},[6691],{"type":46,"value":6692},"Error",{"type":41,"tag":347,"props":6694,"children":6695},{},[6696],{"type":46,"value":6697},"Cause",{"type":41,"tag":347,"props":6699,"children":6700},{},[6701],{"type":46,"value":6702},"Fix",{"type":41,"tag":358,"props":6704,"children":6705},{},[6706,6735,6764,6792,6814,6845,6878,6899,6940,6975,7016,7087,7111,7173],{"type":41,"tag":343,"props":6707,"children":6708},{},[6709,6717,6722],{"type":41,"tag":365,"props":6710,"children":6711},{},[6712],{"type":41,"tag":49,"props":6713,"children":6715},{"className":6714},[],[6716],{"type":46,"value":3036},{"type":41,"tag":365,"props":6718,"children":6719},{},[6720],{"type":46,"value":6721},"A row doesn't match the new schema",{"type":41,"tag":365,"props":6723,"children":6724},{},[6725,6727,6733],{"type":46,"value":6726},"Make the field ",{"type":41,"tag":49,"props":6728,"children":6730},{"className":6729},[],[6731],{"type":46,"value":6732},"v.optional()",{"type":46,"value":6734},", backfill, then tighten",{"type":41,"tag":343,"props":6736,"children":6737},{},[6738,6747,6759],{"type":41,"tag":365,"props":6739,"children":6740},{},[6741],{"type":41,"tag":49,"props":6742,"children":6744},{"className":6743},[],[6745],{"type":46,"value":6746},"ReturnsValidationError",{"type":41,"tag":365,"props":6748,"children":6749},{},[6750,6752,6757],{"type":46,"value":6751},"Returned shape doesn't match ",{"type":41,"tag":49,"props":6753,"children":6755},{"className":6754},[],[6756],{"type":46,"value":2546},{"type":46,"value":6758}," validator",{"type":41,"tag":365,"props":6760,"children":6761},{},[6762],{"type":46,"value":6763},"Map private fields out on read, or update validator",{"type":41,"tag":343,"props":6765,"children":6766},{},[6767,6775,6780],{"type":41,"tag":365,"props":6768,"children":6769},{},[6770],{"type":41,"tag":49,"props":6771,"children":6773},{"className":6772},[],[6774],{"type":46,"value":1090},{"type":41,"tag":365,"props":6776,"children":6777},{},[6778],{"type":46,"value":6779},"Client sent args that don't match validator",{"type":41,"tag":365,"props":6781,"children":6782},{},[6783,6785,6790],{"type":46,"value":6784},"Restart ",{"type":41,"tag":49,"props":6786,"children":6788},{"className":6787},[],[6789],{"type":46,"value":3101},{"type":46,"value":6791}," and client; codegen is stale",{"type":41,"tag":343,"props":6793,"children":6794},{},[6795,6804,6809],{"type":41,"tag":365,"props":6796,"children":6797},{},[6798],{"type":41,"tag":49,"props":6799,"children":6801},{"className":6800},[],[6802],{"type":46,"value":6803},"SystemTimeoutError",{"type":41,"tag":365,"props":6805,"children":6806},{},[6807],{"type":46,"value":6808},"Function exceeded its time limit",{"type":41,"tag":365,"props":6810,"children":6811},{},[6812],{"type":46,"value":6813},"Common cause: many sequential mutations from a Node API route. Batch or move to scheduler",{"type":41,"tag":343,"props":6815,"children":6816},{},[6817,6825,6835],{"type":41,"tag":365,"props":6818,"children":6819},{},[6820],{"type":41,"tag":49,"props":6821,"children":6823},{"className":6822},[],[6824],{"type":46,"value":181},{"type":41,"tag":365,"props":6826,"children":6827},{},[6828,6833],{"type":41,"tag":49,"props":6829,"children":6831},{"className":6830},[],[6832],{"type":46,"value":140},{"type":46,"value":6834}," on a large indexed query",{"type":41,"tag":365,"props":6836,"children":6837},{},[6838,6840],{"type":46,"value":6839},"Paginate or move to background sweep via ",{"type":41,"tag":49,"props":6841,"children":6843},{"className":6842},[],[6844],{"type":46,"value":6161},{"type":41,"tag":343,"props":6846,"children":6847},{},[6848,6857,6862],{"type":41,"tag":365,"props":6849,"children":6850},{},[6851],{"type":41,"tag":49,"props":6852,"children":6854},{"className":6853},[],[6855],{"type":46,"value":6856},"Too many writes in a single function execution",{"type":41,"tag":365,"props":6858,"children":6859},{},[6860],{"type":46,"value":6861},"Single transaction > ~8K writes",{"type":41,"tag":365,"props":6863,"children":6864},{},[6865,6867,6872,6873],{"type":46,"value":6866},"Batch via ",{"type":41,"tag":49,"props":6868,"children":6870},{"className":6869},[],[6871],{"type":46,"value":3306},{"type":46,"value":160},{"type":41,"tag":49,"props":6874,"children":6876},{"className":6875},[],[6877],{"type":46,"value":3314},{"type":41,"tag":343,"props":6879,"children":6880},{},[6881,6889,6894],{"type":41,"tag":365,"props":6882,"children":6883},{},[6884],{"type":41,"tag":49,"props":6885,"children":6887},{"className":6886},[],[6888],{"type":46,"value":3387},{"type":41,"tag":365,"props":6890,"children":6891},{},[6892],{"type":46,"value":6893},"Two mutations stomped on the same doc",{"type":41,"tag":365,"props":6895,"children":6896},{},[6897],{"type":46,"value":6898},"Reduce contention; sharded counters for hot increments",{"type":41,"tag":343,"props":6900,"children":6901},{},[6902,6910,6935],{"type":41,"tag":365,"props":6903,"children":6904},{},[6905],{"type":41,"tag":49,"props":6906,"children":6908},{"className":6907},[],[6909],{"type":46,"value":2966},{"type":41,"tag":365,"props":6911,"children":6912},{},[6913,6915,6921,6922,6928,6930],{"type":46,"value":6914},"Index named ",{"type":41,"tag":49,"props":6916,"children":6918},{"className":6917},[],[6919],{"type":46,"value":6920},"by_id",{"type":46,"value":375},{"type":41,"tag":49,"props":6923,"children":6925},{"className":6924},[],[6926],{"type":46,"value":6927},"by_creation_time",{"type":46,"value":6929},", or starts with ",{"type":41,"tag":49,"props":6931,"children":6933},{"className":6932},[],[6934],{"type":46,"value":2981},{"type":41,"tag":365,"props":6936,"children":6937},{},[6938],{"type":46,"value":6939},"Rename it",{"type":41,"tag":343,"props":6941,"children":6942},{},[6943,6951,6970],{"type":41,"tag":365,"props":6944,"children":6945},{},[6946],{"type":41,"tag":49,"props":6947,"children":6949},{"className":6948},[],[6950],{"type":46,"value":2997},{"type":41,"tag":365,"props":6952,"children":6953},{},[6954,6956,6961,6963,6969],{"type":46,"value":6955},"Table name starts with ",{"type":41,"tag":49,"props":6957,"children":6959},{"className":6958},[],[6960],{"type":46,"value":2981},{"type":46,"value":6962}," (e.g. ",{"type":41,"tag":49,"props":6964,"children":6966},{"className":6965},[],[6967],{"type":46,"value":6968},"_migrations",{"type":46,"value":1931},{"type":41,"tag":365,"props":6971,"children":6972},{},[6973],{"type":46,"value":6974},"Drop the leading underscore",{"type":41,"tag":343,"props":6976,"children":6977},{},[6978,6988,6999],{"type":41,"tag":365,"props":6979,"children":6980},{},[6981,6986],{"type":41,"tag":49,"props":6982,"children":6984},{"className":6983},[],[6985],{"type":46,"value":696},{"type":46,"value":6987}," (or another keyword)",{"type":41,"tag":365,"props":6989,"children":6990},{},[6991,6997],{"type":41,"tag":49,"props":6992,"children":6994},{"className":6993},[],[6995],{"type":46,"value":6996},"export const delete = ...",{"type":46,"value":6998}," — export name is a JS reserved word",{"type":41,"tag":365,"props":7000,"children":7001},{},[7002,7004,7009,7010,7015],{"type":46,"value":7003},"Rename to a synonym (",{"type":41,"tag":49,"props":7005,"children":7007},{"className":7006},[],[7008],{"type":46,"value":768},{"type":46,"value":375},{"type":41,"tag":49,"props":7011,"children":7013},{"className":7012},[],[7014],{"type":46,"value":775},{"type":46,"value":1931},{"type":41,"tag":343,"props":7017,"children":7018},{},[7019,7030,7056],{"type":41,"tag":365,"props":7020,"children":7021},{},[7022,7028],{"type":41,"tag":49,"props":7023,"children":7025},{"className":7024},[],[7026],{"type":46,"value":7027},"use node",{"type":46,"value":7029}," in error",{"type":41,"tag":365,"props":7031,"children":7032},{},[7033,7035,7041,7042,7047,7049,7054],{"type":46,"value":7034},"Imported a Node-only module (e.g. ",{"type":41,"tag":49,"props":7036,"children":7038},{"className":7037},[],[7039],{"type":46,"value":7040},"crypto",{"type":46,"value":375},{"type":41,"tag":49,"props":7043,"children":7045},{"className":7044},[],[7046],{"type":46,"value":813},{"type":46,"value":7048},") into a default V8 file — including ",{"type":41,"tag":49,"props":7050,"children":7052},{"className":7051},[],[7053],{"type":46,"value":864},{"type":46,"value":7055}," route handlers",{"type":41,"tag":365,"props":7057,"children":7058},{},[7059,7061,7066,7068,7073,7075,7080,7082],{"type":46,"value":7060},"Add ",{"type":41,"tag":49,"props":7062,"children":7064},{"className":7063},[],[7065],{"type":46,"value":640},{"type":46,"value":7067}," at the top and move to an action, or use Web Crypto (",{"type":41,"tag":49,"props":7069,"children":7071},{"className":7070},[],[7072],{"type":46,"value":879},{"type":46,"value":7074},") instead of ",{"type":41,"tag":49,"props":7076,"children":7078},{"className":7077},[],[7079],{"type":46,"value":739},{"type":46,"value":7081},"ing ",{"type":41,"tag":49,"props":7083,"children":7085},{"className":7084},[],[7086],{"type":46,"value":7040},{"type":41,"tag":343,"props":7088,"children":7089},{},[7090,7098,7103],{"type":41,"tag":365,"props":7091,"children":7092},{},[7093],{"type":41,"tag":49,"props":7094,"children":7096},{"className":7095},[],[7097],{"type":46,"value":5486},{"type":41,"tag":365,"props":7099,"children":7100},{},[7101],{"type":46,"value":7102},"Convex Auth missing env keys",{"type":41,"tag":365,"props":7104,"children":7105},{},[7106],{"type":41,"tag":49,"props":7107,"children":7109},{"className":7108},[],[7110],{"type":46,"value":5494},{"type":41,"tag":343,"props":7112,"children":7113},{},[7114,7151,7161],{"type":41,"tag":365,"props":7115,"children":7116},{},[7117,7119,7125,7126,7131,7133,7138,7140,7145,7146],{"type":46,"value":7118},"App stuck \"signed out\" — sign-in succeeds, tokens mint, but ",{"type":41,"tag":49,"props":7120,"children":7122},{"className":7121},[],[7123],{"type":46,"value":7124},"getAuthUserId",{"type":46,"value":264},{"type":41,"tag":49,"props":7127,"children":7129},{"className":7128},[],[7130],{"type":46,"value":3949},{"type":46,"value":7132}," is always ",{"type":41,"tag":49,"props":7134,"children":7136},{"className":7135},[],[7137],{"type":46,"value":2591},{"type":46,"value":7139},", queries return ",{"type":41,"tag":49,"props":7141,"children":7143},{"className":7142},[],[7144],{"type":46,"value":5283},{"type":46,"value":375},{"type":41,"tag":87,"props":7147,"children":7148},{},[7149],{"type":46,"value":7150},"no error",{"type":41,"tag":365,"props":7152,"children":7153},{},[7154,7159],{"type":41,"tag":49,"props":7155,"children":7157},{"className":7156},[],[7158],{"type":46,"value":5238},{"type":46,"value":7160}," was never created (no registered JWT issuer)",{"type":41,"tag":365,"props":7162,"children":7163},{},[7164,7166,7171],{"type":46,"value":7165},"Create ",{"type":41,"tag":49,"props":7167,"children":7169},{"className":7168},[],[7170],{"type":46,"value":5238},{"type":46,"value":7172}," (see Auth section) and re-push",{"type":41,"tag":343,"props":7174,"children":7175},{},[7176,7192,7197],{"type":41,"tag":365,"props":7177,"children":7178},{},[7179,7185,7186],{"type":41,"tag":49,"props":7180,"children":7182},{"className":7181},[],[7183],{"type":46,"value":7184},"nonInteractiveError",{"type":46,"value":1152},{"type":41,"tag":49,"props":7187,"children":7189},{"className":7188},[],[7190],{"type":46,"value":7191},"Cannot prompt for input",{"type":41,"tag":365,"props":7193,"children":7194},{},[7195],{"type":46,"value":7196},"TTY-required prompt under a non-TTY harness",{"type":41,"tag":365,"props":7198,"children":7199},{},[7200,7206,7208],{"type":41,"tag":49,"props":7201,"children":7203},{"className":7202},[],[7204],{"type":46,"value":7205},"CONVEX_AGENT_MODE=anonymous",{"type":46,"value":7207}," before ",{"type":41,"tag":49,"props":7209,"children":7211},{"className":7210},[],[7212],{"type":46,"value":7213},"npx convex dev",{"type":41,"tag":111,"props":7215,"children":7217},{"id":7216},"visual-quality-dont-ship-grey-on-grey",[7218],{"type":46,"value":7219},"Visual quality — don't ship grey-on-grey",{"type":41,"tag":42,"props":7221,"children":7222},{},[7223],{"type":46,"value":7224},"Agents reliably ship low-contrast, all-monospace UIs and call them done.",{"type":41,"tag":123,"props":7226,"children":7227},{},[7228,7297,7331,7356,7366],{"type":41,"tag":127,"props":7229,"children":7230},{},[7231,7236,7238,7244,7245,7251,7253,7259,7260,7266,7267,7273,7274,7280,7281,7287,7289,7295],{"type":41,"tag":87,"props":7232,"children":7233},{},[7234],{"type":46,"value":7235},"Use the design system.",{"type":46,"value":7237}," If the project has shadcn\u002Fui (the ",{"type":41,"tag":49,"props":7239,"children":7241},{"className":7240},[],[7242],{"type":46,"value":7243},"nextjs-shadcn",{"type":46,"value":1152},{"type":41,"tag":49,"props":7246,"children":7248},{"className":7247},[],[7249],{"type":46,"value":7250},"nextjs-convexauth-shadcn",{"type":46,"value":7252}," templates do), use ",{"type":41,"tag":49,"props":7254,"children":7256},{"className":7255},[],[7257],{"type":46,"value":7258},"\u003CButton>",{"type":46,"value":375},{"type":41,"tag":49,"props":7261,"children":7263},{"className":7262},[],[7264],{"type":46,"value":7265},"\u003CCard>",{"type":46,"value":375},{"type":41,"tag":49,"props":7268,"children":7270},{"className":7269},[],[7271],{"type":46,"value":7272},"\u003CInput>",{"type":46,"value":375},{"type":41,"tag":49,"props":7275,"children":7277},{"className":7276},[],[7278],{"type":46,"value":7279},"\u003CBadge>",{"type":46,"value":375},{"type":41,"tag":49,"props":7282,"children":7284},{"className":7283},[],[7285],{"type":46,"value":7286},"\u003CTabs>",{"type":46,"value":7288}," everywhere. Never hand-write ",{"type":41,"tag":49,"props":7290,"children":7292},{"className":7291},[],[7293],{"type":46,"value":7294},"\u003Cdiv className=\"bg-zinc-800 …\">",{"type":46,"value":7296}," when a primitive fits.",{"type":41,"tag":127,"props":7298,"children":7299},{},[7300,7305,7307,7313,7315,7321,7323,7329],{"type":41,"tag":87,"props":7301,"children":7302},{},[7303],{"type":46,"value":7304},"≥4:1 contrast",{"type":46,"value":7306}," on borders, dividers, labels. ",{"type":41,"tag":49,"props":7308,"children":7310},{"className":7309},[],[7311],{"type":46,"value":7312},"border-zinc-700",{"type":46,"value":7314}," on ",{"type":41,"tag":49,"props":7316,"children":7318},{"className":7317},[],[7319],{"type":46,"value":7320},"bg-zinc-950",{"type":46,"value":7322}," is too dim — go to ",{"type":41,"tag":49,"props":7324,"children":7326},{"className":7325},[],[7327],{"type":46,"value":7328},"border-zinc-500",{"type":46,"value":7330}," or lighter.",{"type":41,"tag":127,"props":7332,"children":7333},{},[7334,7339,7340,7346,7348,7354],{"type":41,"tag":87,"props":7335,"children":7336},{},[7337],{"type":46,"value":7338},"Saturated accents.",{"type":46,"value":682},{"type":41,"tag":49,"props":7341,"children":7343},{"className":7342},[],[7344],{"type":46,"value":7345},"bg-sky-600 text-white",{"type":46,"value":7347}," for primary actions, not ",{"type":41,"tag":49,"props":7349,"children":7351},{"className":7350},[],[7352],{"type":46,"value":7353},"bg-sky-500\u002F10",{"type":46,"value":7355}," (reads as grey).",{"type":41,"tag":127,"props":7357,"children":7358},{},[7359,7364],{"type":41,"tag":87,"props":7360,"children":7361},{},[7362],{"type":46,"value":7363},"Don't make everything monospace.",{"type":46,"value":7365}," Reserve mono for code; use a sans for UI chrome.",{"type":41,"tag":127,"props":7367,"children":7368},{},[7369,7374],{"type":41,"tag":87,"props":7370,"children":7371},{},[7372],{"type":46,"value":7373},"Canvas \u002F graph libraries need explicit dark-theme overrides.",{"type":46,"value":7375}," React Flow, Cytoscape, Mermaid, vis.js, D3 — all light-mode-first by default and illegible on dark.",{"type":41,"tag":111,"props":7377,"children":7379},{"id":7378},"how-you-write-code",[7380],{"type":46,"value":7381},"How you write code",{"type":41,"tag":123,"props":7383,"children":7384},{},[7385,7403,7429,7476,7493,7510,7563],{"type":41,"tag":127,"props":7386,"children":7387},{},[7388,7393,7395,7401],{"type":41,"tag":87,"props":7389,"children":7390},{},[7391],{"type":46,"value":7392},"Write entire files.",{"type":46,"value":7394}," No ",{"type":41,"tag":49,"props":7396,"children":7398},{"className":7397},[],[7399],{"type":46,"value":7400},"\u002F\u002F ... rest unchanged",{"type":46,"value":7402}," placeholders.",{"type":41,"tag":127,"props":7404,"children":7405},{},[7406,7411,7413,7419,7421,7427],{"type":41,"tag":87,"props":7407,"children":7408},{},[7409],{"type":46,"value":7410},"When you rewrite an existing file, preserve every export it already had.",{"type":46,"value":7412}," Rewriting a module to add a feature is the #1 way functions silently vanish — drop a mutation the frontend imports and ",{"type":41,"tag":49,"props":7414,"children":7416},{"className":7415},[],[7417],{"type":46,"value":7418},"next dev",{"type":46,"value":7420}," still \"compiles clean\" while the browser throws ",{"type":41,"tag":49,"props":7422,"children":7424},{"className":7423},[],[7425],{"type":46,"value":7426},"X is not defined",{"type":46,"value":7428}," at runtime. Before you finish a rewrite, diff your exports against the prior version; a removed export must be deliberate, never incidental.",{"type":41,"tag":127,"props":7430,"children":7431},{},[7432,7445,7447,7452,7454,7459,7461,7467,7469,7474],{"type":41,"tag":87,"props":7433,"children":7434},{},[7435,7437,7443],{"type":46,"value":7436},"Gate on ",{"type":41,"tag":49,"props":7438,"children":7440},{"className":7439},[],[7441],{"type":46,"value":7442},"tsc --noEmit",{"type":46,"value":7444},", not \"it compiled.\"",{"type":46,"value":7446}," A clean Convex push and ",{"type":41,"tag":49,"props":7448,"children":7450},{"className":7449},[],[7451],{"type":46,"value":7418},{"type":46,"value":7453},"'s loose HMR typecheck both miss whole classes of error — a dropped component, a ",{"type":41,"tag":49,"props":7455,"children":7457},{"className":7456},[],[7458],{"type":46,"value":1996},{"type":46,"value":7460}," passed where a branded ",{"type":41,"tag":49,"props":7462,"children":7464},{"className":7463},[],[7465],{"type":46,"value":7466},"Id\u003C...>",{"type":46,"value":7468}," is required, a render-only crash. These surface only in the browser overlay, never in the logs the bootstrap watchers tail. ",{"type":41,"tag":49,"props":7470,"children":7472},{"className":7471},[],[7473],{"type":46,"value":7442},{"type":46,"value":7475}," catches them; treat green tsc, not green HMR, as done.",{"type":41,"tag":127,"props":7477,"children":7478},{},[7479,7484,7486,7491],{"type":41,"tag":87,"props":7480,"children":7481},{},[7482],{"type":46,"value":7483},"After writing",{"type":46,"value":7485},", let ",{"type":41,"tag":49,"props":7487,"children":7489},{"className":7488},[],[7490],{"type":46,"value":3101},{"type":46,"value":7492}," push and report. Fix TS \u002F schema errors in place; re-push. Don't accumulate broken state.",{"type":41,"tag":127,"props":7494,"children":7495},{},[7496,7501,7503,7508],{"type":41,"tag":87,"props":7497,"children":7498},{},[7499],{"type":46,"value":7500},"Verify the watchers fire.",{"type":46,"value":7502}," Function runtime errors over WebSocket land in both ",{"type":41,"tag":49,"props":7504,"children":7506},{"className":7505},[],[7507],{"type":46,"value":3101},{"type":46,"value":7509}," stdout and the browser console; HTTP-action errors only in the calling process's log.",{"type":41,"tag":127,"props":7511,"children":7512},{},[7513,7518,7520,7526,7527,7533,7534,7540,7541,7547,7548,7554,7555,7561],{"type":41,"tag":87,"props":7514,"children":7515},{},[7516],{"type":46,"value":7517},"Use the Convex MCP server when available.",{"type":46,"value":7519}," Tools like ",{"type":41,"tag":49,"props":7521,"children":7523},{"className":7522},[],[7524],{"type":46,"value":7525},"tables",{"type":46,"value":375},{"type":41,"tag":49,"props":7528,"children":7530},{"className":7529},[],[7531],{"type":46,"value":7532},"function-spec",{"type":46,"value":375},{"type":41,"tag":49,"props":7535,"children":7537},{"className":7536},[],[7538],{"type":46,"value":7539},"data",{"type":46,"value":375},{"type":41,"tag":49,"props":7542,"children":7544},{"className":7543},[],[7545],{"type":46,"value":7546},"run-once-query",{"type":46,"value":375},{"type":41,"tag":49,"props":7549,"children":7551},{"className":7550},[],[7552],{"type":46,"value":7553},"logs",{"type":46,"value":375},{"type":41,"tag":49,"props":7556,"children":7558},{"className":7557},[],[7559],{"type":46,"value":7560},"env list\u002Fset\u002Fget",{"type":46,"value":7562}," let you introspect the live deployment rather than guess from generated types.",{"type":41,"tag":127,"props":7564,"children":7565},{},[7566,7571,7573,7578],{"type":41,"tag":87,"props":7567,"children":7568},{},[7569],{"type":46,"value":7570},"Don't ask the user a question you can derive from the schema or guidelines.",{"type":46,"value":7572}," Read ",{"type":41,"tag":49,"props":7574,"children":7576},{"className":7575},[],[7577],{"type":46,"value":97},{"type":46,"value":7579}," first; ask only when you genuinely cannot proceed.",{"type":41,"tag":111,"props":7581,"children":7583},{"id":7582},"keyless-external-apis-server-side",[7584],{"type":46,"value":7585},"Keyless external APIs (server-side)",{"type":41,"tag":42,"props":7587,"children":7588},{},[7589,7591,7596],{"type":46,"value":7590},"Convex functions call external APIs from a ",{"type":41,"tag":87,"props":7592,"children":7593},{},[7594],{"type":46,"value":7595},"server",{"type":46,"value":7597},", not a browser — so any API\nthat keys off the caller's IP, requires a browser origin, or bans datacenter IPs\nwill fail in production even though it \"worked\" from the client during dev. Pick\nkeyless, server-friendly endpoints:",{"type":41,"tag":123,"props":7599,"children":7600},{},[7601,7648,7665],{"type":41,"tag":127,"props":7602,"children":7603},{},[7604,7609,7611,7616,7618,7624,7626,7631,7633,7646],{"type":41,"tag":87,"props":7605,"children":7606},{},[7607],{"type":46,"value":7608},"Reverse geocoding \u002F geocoding:",{"type":46,"value":7610}," use ",{"type":41,"tag":87,"props":7612,"children":7613},{},[7614],{"type":46,"value":7615},"Nominatim",{"type":46,"value":7617}," (OpenStreetMap) with a real\n",{"type":41,"tag":49,"props":7619,"children":7621},{"className":7620},[],[7622],{"type":46,"value":7623},"User-Agent",{"type":46,"value":7625}," header, ≤1 req\u002Fs, and an in-memory cache — or ",{"type":41,"tag":87,"props":7627,"children":7628},{},[7629],{"type":46,"value":7630},"Open-Meteo's",{"type":46,"value":7632},"\ngeocoding endpoint. ",{"type":41,"tag":87,"props":7634,"children":7635},{},[7636,7638,7644],{"type":46,"value":7637},"Avoid ",{"type":41,"tag":49,"props":7639,"children":7641},{"className":7640},[],[7642],{"type":46,"value":7643},"*-client",{"type":46,"value":7645}," SDKs and BigDataCloud's\nreverse-geocode-client",{"type":46,"value":7647}," (browser-only; bans server IPs).",{"type":41,"tag":127,"props":7649,"children":7650},{},[7651,7656,7658,7663],{"type":41,"tag":87,"props":7652,"children":7653},{},[7654],{"type":46,"value":7655},"Weather:",{"type":46,"value":7657}," Open-Meteo (keyless). ",{"type":41,"tag":87,"props":7659,"children":7660},{},[7661],{"type":46,"value":7662},"Transit\u002Ffinance\u002Fsports:",{"type":46,"value":7664}," prefer official\nkeyless real-time endpoints; don't assume a queryable historical dataset exists\n(e.g. there is no general historical Muni on-time API) — verify before designing\naround it.",{"type":41,"tag":127,"props":7666,"children":7667},{},[7668,7670,7675,7676,7682],{"type":46,"value":7669},"Anything requiring a key → put it in a Convex ",{"type":41,"tag":87,"props":7671,"children":7672},{},[7673],{"type":46,"value":7674},"env var",{"type":46,"value":2057},{"type":41,"tag":49,"props":7677,"children":7679},{"className":7678},[],[7680],{"type":46,"value":7681},"npx convex env set",{"type":46,"value":7683},"),\nnever inline; read it server-side.",{"type":41,"tag":42,"props":7685,"children":7686},{},[7687,7692,7694,7699,7701,7707,7709,7714],{"type":41,"tag":87,"props":7688,"children":7689},{},[7690],{"type":46,"value":7691},"Smoke-test before you hand off.",{"type":46,"value":7693}," After ",{"type":41,"tag":49,"props":7695,"children":7697},{"className":7696},[],[7698],{"type":46,"value":3101},{"type":46,"value":7700}," is ready, run ONE realistic\nend-to-end invocation of the main action you wrote (",{"type":41,"tag":49,"props":7702,"children":7704},{"className":7703},[],[7705],{"type":46,"value":7706},"npx convex run \u003Cmodule>:\u003Caction> '{…}'",{"type":46,"value":7708},")\nand assert the key invariants in the result (e.g. string labels aren't ",{"type":41,"tag":49,"props":7710,"children":7712},{"className":7711},[],[7713],{"type":46,"value":2582},{"type":46,"value":7715},",\nthe external call returned data). A clean push is not proof the integration works.",{"type":41,"tag":111,"props":7717,"children":7719},{"id":7718},"further-reading",[7720],{"type":46,"value":7721},"Further reading",{"type":41,"tag":42,"props":7723,"children":7724},{},[7725,7727,7733,7735,7740,7742,7748],{"type":46,"value":7726},"Full canonical rules: ",{"type":41,"tag":5573,"props":7728,"children":7731},{"href":7729,"rel":7730},"https:\u002F\u002Fconvex.link\u002Fconvex_rules.txt",[5577],[7732],{"type":46,"value":7729},{"type":46,"value":7734},". Component catalog: ",{"type":41,"tag":5573,"props":7736,"children":7738},{"href":5575,"rel":7737},[5577],[7739],{"type":46,"value":5575},{"type":46,"value":7741},". Auth docs: ",{"type":41,"tag":5573,"props":7743,"children":7746},{"href":7744,"rel":7745},"https:\u002F\u002Fdocs.convex.dev\u002Fauth\u002Fconvex-auth",[5577],[7747],{"type":46,"value":7744},{"type":46,"value":230},{"type":41,"tag":7750,"props":7751,"children":7752},"style",{},[7753],{"type":46,"value":7754},"html .light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html.light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}",{"items":7756,"total":7934},[7757,7768,7783,7798,7815,7832,7845,7857,7874,7888,7905,7919],{"slug":8,"name":8,"fn":7758,"description":7759,"org":7760,"tags":7761,"stars":7765,"repoUrl":7766,"updatedAt":7767},"guide Convex project setup and usage","Routes general Convex requests to the right project skill. Use when the user asks which Convex skill to use or gives an underspecified Convex app task.",{"slug":8,"name":9,"logoUrl":10,"githubOrg":11},[7762,7763,7764],{"name":14,"slug":15,"type":16},{"name":9,"slug":8,"type":16},{"name":22,"slug":23,"type":16},34,"https:\u002F\u002Fgithub.com\u002Fget-convex\u002Fagent-skills","2026-07-12T08:00:45.091281",{"slug":7769,"name":7769,"fn":7770,"description":7771,"org":7772,"tags":7773,"stars":7765,"repoUrl":7766,"updatedAt":7782},"convex-create-component","build reusable Convex components","Builds reusable Convex components with isolated tables and app-facing APIs. Use for new components, reusable backend modules, integrations, or component boundary work.",{"slug":8,"name":9,"logoUrl":10,"githubOrg":11},[7774,7777,7780,7781],{"name":7775,"slug":7776,"type":16},"API Development","api-development",{"name":7778,"slug":7779,"type":16},"Architecture","architecture",{"name":14,"slug":15,"type":16},{"name":9,"slug":8,"type":16},"2026-07-12T08:00:39.428577",{"slug":7784,"name":7784,"fn":7785,"description":7786,"org":7787,"tags":7788,"stars":7765,"repoUrl":7766,"updatedAt":7797},"convex-migration-helper","plan Convex schema and data migrations","Plans Convex schema and data migrations with widen-migrate-narrow and @convex-dev\u002Fmigrations. Use for breaking schema changes, backfills, table reshaping, or zero-downtime rollouts.",{"slug":8,"name":9,"logoUrl":10,"githubOrg":11},[7789,7790,7791,7794],{"name":14,"slug":15,"type":16},{"name":9,"slug":8,"type":16},{"name":7792,"slug":7793,"type":16},"Data Engineering","data-engineering",{"name":7795,"slug":7796,"type":16},"Migration","migration","2026-07-12T08:00:51.27967",{"slug":7799,"name":7799,"fn":7800,"description":7801,"org":7802,"tags":7803,"stars":7765,"repoUrl":7766,"updatedAt":7814},"convex-performance-audit","audit Convex application performance","Audits Convex performance for reads, subscriptions, write contention, and function limits. Use for slow features, insights findings, OCC conflicts, or read amplification.",{"slug":8,"name":9,"logoUrl":10,"githubOrg":11},[7804,7805,7808,7811],{"name":9,"slug":8,"type":16},{"name":7806,"slug":7807,"type":16},"Debugging","debugging",{"name":7809,"slug":7810,"type":16},"Monitoring","monitoring",{"name":7812,"slug":7813,"type":16},"Performance","performance","2026-07-12T08:00:50.02928",{"slug":7816,"name":7816,"fn":7817,"description":7818,"org":7819,"tags":7820,"stars":7765,"repoUrl":7766,"updatedAt":7831},"convex-quickstart","initialize Convex in applications","Creates or adds Convex to an app. Use for new Convex projects, npm create convex@latest, frontend setup, env vars, or the first npx convex dev run.",{"slug":8,"name":9,"logoUrl":10,"githubOrg":11},[7821,7824,7825,7828],{"name":7822,"slug":7823,"type":16},"CLI","cli",{"name":9,"slug":8,"type":16},{"name":7826,"slug":7827,"type":16},"Frontend","frontend",{"name":7829,"slug":7830,"type":16},"Onboarding","onboarding","2026-07-12T08:00:43.436152",{"slug":7833,"name":7833,"fn":7834,"description":7835,"org":7836,"tags":7837,"stars":7765,"repoUrl":7766,"updatedAt":7844},"convex-setup-auth","set up authentication and access control","Sets up Convex auth, identity mapping, and access control. Use for login, auth providers, users tables, protected functions, or roles in a Convex app.",{"slug":8,"name":9,"logoUrl":10,"githubOrg":11},[7838,7841,7842,7843],{"name":7839,"slug":7840,"type":16},"Access Control","access-control",{"name":3402,"slug":3399,"type":16},{"name":14,"slug":15,"type":16},{"name":9,"slug":8,"type":16},"2026-07-12T08:00:48.652641",{"slug":7846,"name":7846,"fn":7847,"description":7848,"org":7849,"tags":7850,"stars":24,"repoUrl":25,"updatedAt":7856},"add","add capabilities to Convex applications","Add a capability to the CURRENT Convex + Next.js project — consults the served Convex capability catalog for always-current procedures (billing, crons, auth, agent, search, …); falls back to built-in hosting or @convex-dev component search. TRIGGER when the user runs $add, or asks to add hosting\u002Fpublishing or any backend capability to an existing Convex app.",{"slug":8,"name":9,"logoUrl":10,"githubOrg":11},[7851,7852,7853],{"name":14,"slug":15,"type":16},{"name":9,"slug":8,"type":16},{"name":7854,"slug":7855,"type":16},"Next.js","next-js","2026-07-12T07:59:59.358004",{"slug":5972,"name":5972,"fn":7858,"description":7859,"org":7860,"tags":7861,"stars":24,"repoUrl":25,"updatedAt":7873},"build AI agents with Convex","Build an AI agent \u002F RAG feature on Convex (@convex-dev\u002Fagent: threads, tools, vector search). TRIGGER on an AI-agent\u002Fchatbot\u002FRAG request.",{"slug":8,"name":9,"logoUrl":10,"githubOrg":11},[7862,7865,7868,7870],{"name":7863,"slug":7864,"type":16},"Agents","agents",{"name":7866,"slug":7867,"type":16},"Engineering","engineering",{"name":6118,"slug":7869,"type":16},"rag",{"name":7871,"slug":7872,"type":16},"Search","search","2026-07-12T08:00:01.921824",{"slug":3399,"name":3399,"fn":7875,"description":7876,"org":7877,"tags":7878,"stars":24,"repoUrl":25,"updatedAt":7887},"add authentication to Convex applications","Add sign-in (passkeys by default, OAuth\u002Fpassword optional) to the current Convex app, wired correctly incl. auth.config.ts. TRIGGER on a login\u002Fauth request for an existing app.",{"slug":8,"name":9,"logoUrl":10,"githubOrg":11},[7879,7880,7883,7884],{"name":3402,"slug":3399,"type":16},{"name":7881,"slug":7882,"type":16},"Authentication","authentication",{"name":9,"slug":8,"type":16},{"name":7885,"slug":7886,"type":16},"OAuth","oauth","2026-07-18T05:12:54.443056",{"slug":7889,"name":7889,"fn":7890,"description":7891,"org":7892,"tags":7893,"stars":24,"repoUrl":25,"updatedAt":7904},"billing","integrate Stripe billing in Convex apps","Add Stripe billing to a Convex app via @convex-dev\u002Fstripe (checkout + auto-verified webhook + subscription gating). TRIGGER on a payments\u002Fbilling\u002Fsubscription request.",{"slug":8,"name":9,"logoUrl":10,"githubOrg":11},[7894,7895,7898,7901],{"name":9,"slug":8,"type":16},{"name":7896,"slug":7897,"type":16},"Payments","payments",{"name":7899,"slug":7900,"type":16},"Stripe","stripe",{"name":7902,"slug":7903,"type":16},"Webhooks","webhooks","2026-07-12T08:00:08.123246",{"slug":7906,"name":7906,"fn":7907,"description":7908,"org":7909,"tags":7910,"stars":24,"repoUrl":25,"updatedAt":7918},"check-updates","upgrade Convex component versions","Check the CURRENT Convex app's pinned components against the latest recommended versions and offer to upgrade them — e.g. the passkey auth component's new email-first sign-in. TRIGGER when the user runs \u002Fcheck-updates or $check-updates, asks 'are my components up to date', 'any updates', 'upgrade auth', 'upgrade my components', or wants the newest features after a quickstart. Applies each upgrade behind a build gate (verify-or-revert) with the user's consent.",{"slug":8,"name":9,"logoUrl":10,"githubOrg":11},[7911,7914,7915],{"name":7912,"slug":7913,"type":16},"Configuration","configuration",{"name":9,"slug":8,"type":16},{"name":7916,"slug":7917,"type":16},"Maintenance","maintenance","2026-07-12T08:00:03.236862",{"slug":7920,"name":7920,"fn":7921,"description":7922,"org":7923,"tags":7924,"stars":24,"repoUrl":25,"updatedAt":7933},"convex-authz","audit and harden Convex authorization","Audit and harden a Convex app's authorization: identity-from-arg impersonation, missing per-document ownership checks, and public queries leaking PII\u002Ffinancial data by a client-supplied id — the single largest real-defect cluster measured against generated Convex backends (44 of 214). Runs a deterministic scan for the 3 shapes, then applies the canonical requireIdentity\u002FrequireOwner pattern, then verifies with tsc. TRIGGER on 'secure my app', 'audit auth', 'add login', 'who can access this data', or an explicit 'audit my authz'. NOT always-on. SKIP when there is no convex\u002F directory.",{"slug":8,"name":9,"logoUrl":10,"githubOrg":11},[7925,7926,7929,7930],{"name":3402,"slug":3399,"type":16},{"name":7927,"slug":7928,"type":16},"Code Analysis","code-analysis",{"name":9,"slug":8,"type":16},{"name":7931,"slug":7932,"type":16},"Security","security","2026-07-12T08:00:04.516752",26,{"items":7936,"total":2345},[7937,7943,7950,7957,7964,7970,7977],{"slug":7846,"name":7846,"fn":7847,"description":7848,"org":7938,"tags":7939,"stars":24,"repoUrl":25,"updatedAt":7856},{"slug":8,"name":9,"logoUrl":10,"githubOrg":11},[7940,7941,7942],{"name":14,"slug":15,"type":16},{"name":9,"slug":8,"type":16},{"name":7854,"slug":7855,"type":16},{"slug":5972,"name":5972,"fn":7858,"description":7859,"org":7944,"tags":7945,"stars":24,"repoUrl":25,"updatedAt":7873},{"slug":8,"name":9,"logoUrl":10,"githubOrg":11},[7946,7947,7948,7949],{"name":7863,"slug":7864,"type":16},{"name":7866,"slug":7867,"type":16},{"name":6118,"slug":7869,"type":16},{"name":7871,"slug":7872,"type":16},{"slug":3399,"name":3399,"fn":7875,"description":7876,"org":7951,"tags":7952,"stars":24,"repoUrl":25,"updatedAt":7887},{"slug":8,"name":9,"logoUrl":10,"githubOrg":11},[7953,7954,7955,7956],{"name":3402,"slug":3399,"type":16},{"name":7881,"slug":7882,"type":16},{"name":9,"slug":8,"type":16},{"name":7885,"slug":7886,"type":16},{"slug":7889,"name":7889,"fn":7890,"description":7891,"org":7958,"tags":7959,"stars":24,"repoUrl":25,"updatedAt":7904},{"slug":8,"name":9,"logoUrl":10,"githubOrg":11},[7960,7961,7962,7963],{"name":9,"slug":8,"type":16},{"name":7896,"slug":7897,"type":16},{"name":7899,"slug":7900,"type":16},{"name":7902,"slug":7903,"type":16},{"slug":7906,"name":7906,"fn":7907,"description":7908,"org":7965,"tags":7966,"stars":24,"repoUrl":25,"updatedAt":7918},{"slug":8,"name":9,"logoUrl":10,"githubOrg":11},[7967,7968,7969],{"name":7912,"slug":7913,"type":16},{"name":9,"slug":8,"type":16},{"name":7916,"slug":7917,"type":16},{"slug":7920,"name":7920,"fn":7921,"description":7922,"org":7971,"tags":7972,"stars":24,"repoUrl":25,"updatedAt":7933},{"slug":8,"name":9,"logoUrl":10,"githubOrg":11},[7973,7974,7975,7976],{"name":3402,"slug":3399,"type":16},{"name":7927,"slug":7928,"type":16},{"name":9,"slug":8,"type":16},{"name":7931,"slug":7932,"type":16},{"slug":4,"name":4,"fn":5,"description":6,"org":7978,"tags":7979,"stars":24,"repoUrl":25,"updatedAt":26},{"slug":8,"name":9,"logoUrl":10,"githubOrg":11},[7980,7981,7982,7983],{"name":14,"slug":15,"type":16},{"name":9,"slug":8,"type":16},{"name":22,"slug":23,"type":16},{"name":19,"slug":20,"type":16}]