opencastle 0.33.9 → 0.34.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (112) hide show
  1. package/dist/cli/init.d.ts.map +1 -1
  2. package/dist/cli/init.js +39 -17
  3. package/dist/cli/init.js.map +1 -1
  4. package/dist/cli/stack-config.d.ts.map +1 -1
  5. package/dist/cli/stack-config.js +5 -0
  6. package/dist/cli/stack-config.js.map +1 -1
  7. package/dist/cli/types.d.ts +1 -1
  8. package/dist/cli/types.d.ts.map +1 -1
  9. package/dist/orchestrator/plugins/cloudflare/config.d.ts +3 -0
  10. package/dist/orchestrator/plugins/cloudflare/config.d.ts.map +1 -0
  11. package/dist/orchestrator/plugins/cloudflare/config.js +23 -0
  12. package/dist/orchestrator/plugins/cloudflare/config.js.map +1 -0
  13. package/dist/orchestrator/plugins/coolify/config.d.ts +3 -0
  14. package/dist/orchestrator/plugins/coolify/config.d.ts.map +1 -0
  15. package/dist/orchestrator/plugins/coolify/config.js +28 -0
  16. package/dist/orchestrator/plugins/coolify/config.js.map +1 -0
  17. package/dist/orchestrator/plugins/drizzle/config.d.ts +3 -0
  18. package/dist/orchestrator/plugins/drizzle/config.d.ts.map +1 -0
  19. package/dist/orchestrator/plugins/drizzle/config.js +15 -0
  20. package/dist/orchestrator/plugins/drizzle/config.js.map +1 -0
  21. package/dist/orchestrator/plugins/expo/config.d.ts +3 -0
  22. package/dist/orchestrator/plugins/expo/config.d.ts.map +1 -0
  23. package/dist/orchestrator/plugins/expo/config.js +23 -0
  24. package/dist/orchestrator/plugins/expo/config.js.map +1 -0
  25. package/dist/orchestrator/plugins/index.d.ts.map +1 -1
  26. package/dist/orchestrator/plugins/index.js +12 -0
  27. package/dist/orchestrator/plugins/index.js.map +1 -1
  28. package/dist/orchestrator/plugins/sentry/config.d.ts +3 -0
  29. package/dist/orchestrator/plugins/sentry/config.d.ts.map +1 -0
  30. package/dist/orchestrator/plugins/sentry/config.js +28 -0
  31. package/dist/orchestrator/plugins/sentry/config.js.map +1 -0
  32. package/dist/orchestrator/plugins/stripe/config.d.ts +3 -0
  33. package/dist/orchestrator/plugins/stripe/config.d.ts.map +1 -0
  34. package/dist/orchestrator/plugins/stripe/config.js +42 -0
  35. package/dist/orchestrator/plugins/stripe/config.js.map +1 -0
  36. package/dist/orchestrator/plugins/types.d.ts +1 -1
  37. package/dist/orchestrator/plugins/types.d.ts.map +1 -1
  38. package/package.json +1 -1
  39. package/src/cli/init.ts +43 -22
  40. package/src/cli/stack-config.ts +5 -0
  41. package/src/cli/types.ts +1 -1
  42. package/src/dashboard/dist/data/convoys/demo-api-v2.json +3 -3
  43. package/src/dashboard/dist/data/convoys/demo-auth-revamp.json +4 -4
  44. package/src/dashboard/dist/data/convoys/demo-dashboard-ui.json +12 -12
  45. package/src/dashboard/dist/data/convoys/demo-data-pipeline.json +3 -3
  46. package/src/dashboard/dist/data/convoys/demo-deploy-ci.json +1 -1
  47. package/src/dashboard/dist/data/convoys/demo-docs-update.json +3 -3
  48. package/src/dashboard/dist/data/convoys/demo-perf-opt.json +4 -4
  49. package/src/dashboard/node_modules/.vite/deps/_metadata.json +6 -6
  50. package/src/dashboard/public/data/convoys/demo-api-v2.json +3 -3
  51. package/src/dashboard/public/data/convoys/demo-auth-revamp.json +4 -4
  52. package/src/dashboard/public/data/convoys/demo-dashboard-ui.json +12 -12
  53. package/src/dashboard/public/data/convoys/demo-data-pipeline.json +3 -3
  54. package/src/dashboard/public/data/convoys/demo-deploy-ci.json +1 -1
  55. package/src/dashboard/public/data/convoys/demo-docs-update.json +3 -3
  56. package/src/dashboard/public/data/convoys/demo-perf-opt.json +4 -4
  57. package/src/orchestrator/customizations/agents/skill-matrix.json +24 -4
  58. package/src/orchestrator/customizations/agents/skill-matrix.md +5 -0
  59. package/src/orchestrator/plugins/cloudflare/SKILL.md +111 -0
  60. package/src/orchestrator/plugins/cloudflare/config.ts +24 -0
  61. package/src/orchestrator/plugins/cloudflare/references/deployment.md +147 -0
  62. package/src/orchestrator/plugins/cloudflare/references/storage.md +118 -0
  63. package/src/orchestrator/plugins/cloudflare/references/workers.md +135 -0
  64. package/src/orchestrator/plugins/convex/SKILL.md +62 -20
  65. package/src/orchestrator/plugins/convex/references/auth-auth0.md +116 -0
  66. package/src/orchestrator/plugins/convex/references/auth-clerk.md +113 -0
  67. package/src/orchestrator/plugins/convex/references/auth-convex-auth.md +143 -0
  68. package/src/orchestrator/plugins/convex/references/auth-setup.md +87 -0
  69. package/src/orchestrator/plugins/convex/references/auth-workos.md +114 -0
  70. package/src/orchestrator/plugins/convex/references/components-advanced.md +134 -0
  71. package/src/orchestrator/plugins/convex/references/components.md +171 -0
  72. package/src/orchestrator/plugins/convex/references/function-budget.md +232 -0
  73. package/src/orchestrator/plugins/convex/references/hot-path-rules.md +371 -0
  74. package/src/orchestrator/plugins/convex/references/migrations-component.md +170 -0
  75. package/src/orchestrator/plugins/convex/references/migrations.md +259 -0
  76. package/src/orchestrator/plugins/convex/references/occ-conflicts.md +126 -0
  77. package/src/orchestrator/plugins/convex/references/performance-audit.md +80 -0
  78. package/src/orchestrator/plugins/convex/references/quickstart.md +176 -0
  79. package/src/orchestrator/plugins/convex/references/subscription-cost.md +252 -0
  80. package/src/orchestrator/plugins/coolify/SKILL.md +134 -0
  81. package/src/orchestrator/plugins/coolify/config.ts +29 -0
  82. package/src/orchestrator/plugins/coolify/references/applications.md +65 -0
  83. package/src/orchestrator/plugins/coolify/references/ci-cd-webhooks.md +73 -0
  84. package/src/orchestrator/plugins/coolify/references/databases-services.md +57 -0
  85. package/src/orchestrator/plugins/coolify/references/docker-compose.md +121 -0
  86. package/src/orchestrator/plugins/coolify/references/infrastructure.md +77 -0
  87. package/src/orchestrator/plugins/drizzle/SKILL.md +123 -0
  88. package/src/orchestrator/plugins/drizzle/config.ts +16 -0
  89. package/src/orchestrator/plugins/drizzle/references/migrations.md +112 -0
  90. package/src/orchestrator/plugins/drizzle/references/query-patterns.md +127 -0
  91. package/src/orchestrator/plugins/drizzle/references/schema-patterns.md +105 -0
  92. package/src/orchestrator/plugins/expo/SKILL.md +114 -0
  93. package/src/orchestrator/plugins/expo/config.ts +24 -0
  94. package/src/orchestrator/plugins/expo/references/eas-build.md +73 -0
  95. package/src/orchestrator/plugins/expo/references/native-modules.md +71 -0
  96. package/src/orchestrator/plugins/expo/references/routing.md +83 -0
  97. package/src/orchestrator/plugins/index.ts +12 -0
  98. package/src/orchestrator/plugins/linear/SKILL.md +21 -3
  99. package/src/orchestrator/plugins/sentry/SKILL.md +94 -0
  100. package/src/orchestrator/plugins/sentry/config.ts +29 -0
  101. package/src/orchestrator/plugins/sentry/references/error-patterns.md +112 -0
  102. package/src/orchestrator/plugins/sentry/references/performance.md +66 -0
  103. package/src/orchestrator/plugins/sentry/references/sdk-setup.md +108 -0
  104. package/src/orchestrator/plugins/stripe/SKILL.md +138 -0
  105. package/src/orchestrator/plugins/stripe/config.ts +43 -0
  106. package/src/orchestrator/plugins/stripe/references/api-patterns.md +57 -0
  107. package/src/orchestrator/plugins/stripe/references/projects-setup.md +30 -0
  108. package/src/orchestrator/plugins/stripe/references/upgrade-guide.md +105 -0
  109. package/src/orchestrator/plugins/types.ts +1 -1
  110. package/src/orchestrator/skills/backbone-scaffolding/EXAMPLES.md +1 -1
  111. package/src/orchestrator/skills/backbone-scaffolding/SKILL.md +32 -16
  112. package/src/orchestrator/plugins/convex/REFERENCE.md +0 -9
@@ -0,0 +1,259 @@
1
+ # Convex Schema & Data Migrations
2
+
3
+ Safe migration of Convex schemas and data when making breaking changes.
4
+
5
+ ## Core Constraint
6
+
7
+ Convex will not let you deploy a schema that does not match the data at rest:
8
+ - Cannot add a required field if existing documents don't have it
9
+ - Cannot change a field's type if existing documents have the old type
10
+ - Cannot remove a field from the schema if existing documents still have it
11
+
12
+ ## Safe Changes (No Migration Needed)
13
+
14
+ Adding an optional field or a new table requires no migration:
15
+
16
+ ```typescript
17
+ // Adding optional field — safe
18
+ users: defineTable({
19
+ name: v.string(),
20
+ bio: v.optional(v.string()), // new optional field
21
+ })
22
+
23
+ // Adding new table — safe
24
+ posts: defineTable({
25
+ userId: v.id("users"),
26
+ title: v.string(),
27
+ }).index("by_user", ["userId"])
28
+ ```
29
+
30
+ ## Widen-Migrate-Narrow Workflow
31
+
32
+ Every breaking migration follows this multi-deploy pattern:
33
+
34
+ **Deploy 1 — Widen:**
35
+ 1. Update schema to allow both old and new formats (e.g., add optional new field)
36
+ 2. Update code to handle both formats when reading
37
+ 3. Update code to write the new format for new documents
38
+ 4. Deploy
39
+
40
+ **Between deploys — Migrate data:**
41
+ 5. Run migration to backfill existing documents
42
+ 6. Verify all documents are migrated
43
+
44
+ **Deploy 2 — Narrow:**
45
+ 7. Update schema to require the new format only
46
+ 8. Remove code that handles the old format
47
+ 9. Deploy
48
+
49
+ ## Common Patterns
50
+
51
+ ### Adding a Required Field
52
+
53
+ ```typescript
54
+ // Deploy 1: make optional
55
+ users: defineTable({
56
+ name: v.string(),
57
+ role: v.optional(v.union(v.literal("user"), v.literal("admin"))),
58
+ })
59
+
60
+ // Migration: backfill
61
+ export const addDefaultRole = migrations.define({
62
+ table: "users",
63
+ migrateOne: async (ctx, user) => {
64
+ if (user.role === undefined) {
65
+ await ctx.db.patch(user._id, { role: "user" });
66
+ }
67
+ },
68
+ });
69
+
70
+ // Deploy 2: make required
71
+ users: defineTable({
72
+ name: v.string(),
73
+ role: v.union(v.literal("user"), v.literal("admin")),
74
+ })
75
+ ```
76
+
77
+ ### Deleting a Field
78
+
79
+ ```typescript
80
+ // Deploy 1: Make optional
81
+ // isPro: v.boolean() → isPro: v.optional(v.boolean())
82
+
83
+ // Migration: clear the field
84
+ export const removeIsPro = migrations.define({
85
+ table: "teams",
86
+ migrateOne: async (ctx, team) => {
87
+ if (team.isPro !== undefined) {
88
+ await ctx.db.patch(team._id, { isPro: undefined });
89
+ }
90
+ },
91
+ });
92
+
93
+ // Deploy 2: Remove isPro from schema entirely
94
+ ```
95
+
96
+ ### Changing a Field Type
97
+
98
+ Create a new field rather than modifying the existing one:
99
+
100
+ ```typescript
101
+ // Deploy 1: Add new field, keep old field optional
102
+ // isPro: v.boolean() → isPro: v.optional(v.boolean()), plan: v.optional(...)
103
+
104
+ // Migration: convert old to new
105
+ export const convertToEnum = migrations.define({
106
+ table: "teams",
107
+ migrateOne: async (ctx, team) => {
108
+ if (team.plan === undefined) {
109
+ await ctx.db.patch(team._id, {
110
+ plan: team.isPro ? "pro" : "basic",
111
+ isPro: undefined,
112
+ });
113
+ }
114
+ },
115
+ });
116
+
117
+ // Deploy 2: Remove isPro, make plan required
118
+ ```
119
+
120
+ ### Splitting Nested Data Into a Separate Table
121
+
122
+ ```typescript
123
+ export const extractPreferences = migrations.define({
124
+ table: "users",
125
+ migrateOne: async (ctx, user) => {
126
+ if (user.preferences === undefined) return;
127
+ const existing = await ctx.db
128
+ .query("userPreferences")
129
+ .withIndex("by_user", (q) => q.eq("userId", user._id))
130
+ .first();
131
+ if (!existing) {
132
+ await ctx.db.insert("userPreferences", {
133
+ userId: user._id,
134
+ ...user.preferences,
135
+ });
136
+ }
137
+ await ctx.db.patch(user._id, { preferences: undefined });
138
+ },
139
+ });
140
+ ```
141
+
142
+ ## @convex-dev/migrations Component
143
+
144
+ For any non-trivial migration, use this component — it handles batching, cursor-based pagination, state tracking, resume from failure, dry runs, and progress monitoring.
145
+
146
+ See `references/migrations-component.md` for installation, setup, and full API.
147
+
148
+ ```bash
149
+ npm install @convex-dev/migrations
150
+ ```
151
+
152
+ ```typescript
153
+ // convex/convex.config.ts
154
+ import migrations from "@convex-dev/migrations/convex.config.js";
155
+ const app = defineApp();
156
+ app.use(migrations);
157
+
158
+ // convex/migrations.ts
159
+ import { Migrations } from "@convex-dev/migrations";
160
+ export const migrations = new Migrations<DataModel>(components.migrations);
161
+ export const run = migrations.runner();
162
+ ```
163
+
164
+ Run from CLI:
165
+
166
+ ```bash
167
+ npx convex run migrations:run '{"fn": "migrations:addDefaultRole"}'
168
+ # Dry run first:
169
+ npx convex run migrations:runIt '{"dryRun": true}'
170
+ # Check status:
171
+ npx convex run --component migrations lib:getStatus --watch
172
+ ```
173
+
174
+ ### Small Table Shortcut
175
+
176
+ For tables with only a few thousand documents, use a single `internalMutation` without the component:
177
+
178
+ ```typescript
179
+ export const backfillSmallTable = internalMutation({
180
+ handler: async (ctx) => {
181
+ const docs = await ctx.db.query("smallConfig").collect();
182
+ for (const doc of docs) {
183
+ if (doc.newField === undefined) {
184
+ await ctx.db.patch(doc._id, { newField: "default" });
185
+ }
186
+ }
187
+ },
188
+ });
189
+ ```
190
+
191
+ ## Zero-Downtime Strategies
192
+
193
+ ### Dual Write (Preferred)
194
+
195
+ Write to both old and new structures. Read from old until migration is complete. Safe to roll back at any point.
196
+
197
+ ```typescript
198
+ // Good: writing both formats during migration
199
+ export const createTeam = mutation({
200
+ handler: async (ctx, args) => {
201
+ const plan = args.isPro ? "pro" : "basic";
202
+ await ctx.db.insert("teams", {
203
+ name: args.name,
204
+ isPro: args.isPro, // old format
205
+ plan, // new format
206
+ });
207
+ },
208
+ });
209
+ ```
210
+
211
+ ### Dual Read
212
+
213
+ Read both formats (preferring new), write only the new format:
214
+
215
+ ```typescript
216
+ function getTeamPlan(team: Doc<"teams">): "basic" | "pro" {
217
+ if (team.plan !== undefined) return team.plan;
218
+ return team.isPro ? "pro" : "basic";
219
+ }
220
+ ```
221
+
222
+ ## Verification
223
+
224
+ Query to check remaining unmigrated documents:
225
+
226
+ ```typescript
227
+ export const verifyMigration = query({
228
+ handler: async (ctx) => {
229
+ const remaining = await ctx.db
230
+ .query("users")
231
+ .filter((q) => q.eq(q.field("role"), undefined))
232
+ .take(10);
233
+ return { complete: remaining.length === 0 };
234
+ },
235
+ });
236
+ ```
237
+
238
+ ## Common Pitfalls
239
+
240
+ 1. **Making a field required before migrating data** — deploy rejects because documents lack the field
241
+ 2. **Using `.collect()` on large tables** — hits transaction limits; use the migrations component
242
+ 3. **Not writing the new format before migrating** — creates missed documents during migration window
243
+ 4. **Skipping the dry run** — use `dryRun: true` to validate before touching production data
244
+ 5. **Deleting fields prematurely** — prefer `v.optional` with a deprecation comment
245
+
246
+ ## Migration Checklist
247
+
248
+ - [ ] Identified the breaking change and planned the multi-deploy workflow
249
+ - [ ] Widened schema to allow both old and new formats
250
+ - [ ] Updated code to handle both formats when reading
251
+ - [ ] Updated code to write the new format for new documents
252
+ - [ ] Deployed widened schema
253
+ - [ ] Defined migration using `@convex-dev/migrations`
254
+ - [ ] Tested with `dryRun: true`
255
+ - [ ] Ran migration and monitored status
256
+ - [ ] Verified all documents are migrated
257
+ - [ ] Narrowed schema to require new format only
258
+ - [ ] Cleaned up code handling old format
259
+ - [ ] Deployed final schema
@@ -0,0 +1,126 @@
1
+ # OCC Conflict Resolution
2
+
3
+ Use these rules when insights, logs, or dashboard health show OCC (Optimistic Concurrency Control) conflicts, mutation retries, or write contention on hot tables.
4
+
5
+ ## Core Principle
6
+
7
+ Convex uses optimistic concurrency control. When two transactions read or write overlapping data, one succeeds and the other retries automatically. High contention means wasted work and increased latency.
8
+
9
+ ## Symptoms
10
+
11
+ - OCC conflict errors in deployment logs or health page
12
+ - Mutations retrying multiple times before succeeding
13
+ - User-visible latency spikes on write-heavy pages
14
+ - `npx convex insights --details` showing high conflict rates
15
+
16
+ ## Common Causes
17
+
18
+ ### Hot documents
19
+
20
+ Multiple mutations writing to the same document concurrently. Classic examples: a global counter, a shared settings row, or a "last updated" timestamp on a parent record.
21
+
22
+ ### Broad read sets causing false conflicts
23
+
24
+ A query that scans a large table range creates a broad read set. If any write touches that range, the query's transaction conflicts even if the specific document the query cared about was not modified.
25
+
26
+ ### Fan-out from triggers or cascading writes
27
+
28
+ A single user action triggers multiple mutations that all touch related documents. Each mutation competes with the others.
29
+
30
+ Database triggers (e.g. from `convex-helpers`) run inside the same transaction as the mutation that caused them. If a trigger does heavy work, reads extra tables, or writes to many documents, it extends the transaction's read/write set and increases the window for conflicts. Keep trigger logic minimal, or move expensive derived work to a scheduled function.
31
+
32
+ ### Write-then-read chains
33
+
34
+ A mutation writes a document, then a reactive query re-reads it, then another mutation writes it again. Under load, these chains stack up.
35
+
36
+ ## Fix Order
37
+
38
+ ### 1. Reduce read set size
39
+
40
+ Narrower reads mean fewer false conflicts.
41
+
42
+ ```ts
43
+ // Bad: broad scan creates a wide conflict surface
44
+ const allTasks = await ctx.db.query("tasks").collect();
45
+ const mine = allTasks.filter((t) => t.ownerId === userId);
46
+ ```
47
+
48
+ ```ts
49
+ // Good: indexed query touches only relevant documents
50
+ const mine = await ctx.db
51
+ .query("tasks")
52
+ .withIndex("by_owner", (q) => q.eq("ownerId", userId))
53
+ .collect();
54
+ ```
55
+
56
+ ### 2. Split hot documents
57
+
58
+ When many writers target the same document, split the contention point.
59
+
60
+ ```ts
61
+ // Bad: every vote increments the same counter document
62
+ const counter = await ctx.db.get(pollCounterId);
63
+ await ctx.db.patch(pollCounterId, { count: counter!.count + 1 });
64
+ ```
65
+
66
+ ```ts
67
+ // Good: shard the counter across multiple documents, aggregate on read
68
+ const shardIndex = Math.floor(Math.random() * SHARD_COUNT);
69
+ const shardId = shardIds[shardIndex];
70
+ const shard = await ctx.db.get(shardId);
71
+ await ctx.db.patch(shardId, { count: shard!.count + 1 });
72
+ ```
73
+
74
+ Aggregate the shards in a query or scheduled job when you need the total.
75
+
76
+ ### 3. Skip no-op writes
77
+
78
+ Writes that do not change data still participate in conflict detection and trigger invalidation.
79
+
80
+ ```ts
81
+ // Bad: patches even when nothing changed
82
+ await ctx.db.patch(doc._id, { status: args.status });
83
+ ```
84
+
85
+ ```ts
86
+ // Good: only write when the value actually differs
87
+ if (doc.status !== args.status) {
88
+ await ctx.db.patch(doc._id, { status: args.status });
89
+ }
90
+ ```
91
+
92
+ ### 4. Move non-critical work to scheduled functions
93
+
94
+ If a mutation does primary work plus secondary bookkeeping (analytics, notifications, cache warming), the bookkeeping extends the transaction's lifetime and read/write set.
95
+
96
+ ```ts
97
+ // Bad: analytics update in the same transaction as the user action
98
+ await ctx.db.patch(userId, { lastActiveAt: Date.now() });
99
+ await ctx.db.insert("analytics", { event: "action", userId, ts: Date.now() });
100
+ ```
101
+
102
+ ```ts
103
+ // Good: schedule the bookkeeping so the primary transaction is smaller
104
+ await ctx.db.patch(userId, { lastActiveAt: Date.now() });
105
+ await ctx.scheduler.runAfter(0, internal.analytics.recordEvent, {
106
+ event: "action",
107
+ userId,
108
+ });
109
+ ```
110
+
111
+ ### 5. Combine competing writes
112
+
113
+ If two mutations must update the same document atomically, consider whether they can be combined into a single mutation call from the client, reducing round trips and conflict windows.
114
+
115
+ Do not introduce artificial locks or queues unless the above steps have been tried first.
116
+
117
+ ## Related: Invalidation Scope
118
+
119
+ Splitting hot documents also reduces subscription invalidation, not just OCC contention. If a document is written frequently and read by many queries, those queries re-run on every write even when the fields they care about have not changed. See `subscription-cost.md` section 4 ("Isolate frequently-updated fields") for that pattern.
120
+
121
+ ## Verification
122
+
123
+ 1. OCC conflict rate has dropped in insights or dashboard
124
+ 2. Mutation latency is lower and more consistent
125
+ 3. No data correctness regressions from splitting or scheduling changes
126
+ 4. Sibling writers to the same hot documents were fixed consistently
@@ -0,0 +1,80 @@
1
+ # Convex Performance Audit
2
+
3
+ Diagnose and fix performance problems in Convex applications, one problem class at a time.
4
+
5
+ ## First Step: Gather Signals
6
+
7
+ 1. Run `npx convex insights --details` (use `--prod`, `--preview-name`, or `--deployment-name` as needed)
8
+ 2. If CLI is too old: `npx -y convex@latest insights --details`
9
+ 3. If runtime signals unavailable, audit from code — but keep guardrails: don't recommend structural work without a measured signal
10
+
11
+ ## Signal Routing
12
+
13
+ After gathering signals, identify the problem class and read the matching reference file:
14
+
15
+ | Signal | Reference |
16
+ |--------|-----------|
17
+ | High bytes or documents read, JS filtering, unnecessary joins | `references/hot-path-rules.md` |
18
+ | OCC conflict errors, write contention, mutation retries | `references/occ-conflicts.md` |
19
+ | High subscription count, slow UI updates, excessive re-renders | `references/subscription-cost.md` |
20
+ | Function timeouts, transaction size errors, large payloads | `references/function-budget.md` |
21
+ | General "it's slow" with no specific signal | Start with `references/hot-path-rules.md` |
22
+
23
+ Multiple problem classes can overlap. Read the most relevant reference first.
24
+
25
+ ## Workflow
26
+
27
+ ### 1. Scope the problem
28
+
29
+ Pick one concrete user flow. Write down:
30
+ - Entrypoint functions
31
+ - Client callsites (`useQuery`, `usePaginatedQuery`, `useMutation`)
32
+ - Tables read and tables written
33
+ - Whether the path is high-read, high-write, or both
34
+
35
+ ### 2. Trace the full read and write set
36
+
37
+ For each function:
38
+ 1. Trace every `ctx.db.get()` and `ctx.db.query()`
39
+ 2. Trace every `ctx.db.patch()`, `ctx.db.replace()`, `ctx.db.insert()`
40
+ 3. Note foreign-key lookups, JS-side filtering, and full-document reads
41
+ 4. Identify sibling functions touching the same tables
42
+ 5. Identify reactive stats/aggregates on the same page
43
+
44
+ ### 3. Apply fixes from the relevant reference
45
+
46
+ Read the reference file matching your problem class. Each reference includes specific patterns and a recommended fix order.
47
+
48
+ Do not stop at the single function named by an insight. Trace sibling readers and writers touching the same tables.
49
+
50
+ ### 4. Fix sibling functions together
51
+
52
+ When one function has a bug, audit sibling functions for the same pattern. If one list query switches to a digest table, inspect the other list queries for that table. If one mutation needs no-op write protection, inspect all other writers to the same table.
53
+
54
+ ### 5. Escalate invasive fixes
55
+
56
+ If the fix is invasive, cross-cutting, or migration-heavy:
57
+ - Introducing digest or summary tables across multiple flows
58
+ - Splitting documents to isolate frequently-updated fields
59
+ - Reworking pagination strategy across several screens
60
+ - Switching to a new index that needs migration-safe rollout
61
+
62
+ Stop and present options before editing. Consult `references/migrations.md` when correctness depends on handling old and new states during rollout.
63
+
64
+ ### 6. Verify
65
+
66
+ Confirm:
67
+ 1. Results are the same as before — no dropped records
68
+ 2. Eliminated reads/writes are no longer in the hot path
69
+ 3. Fallback behavior works when denormalized/indexed fields are missing
70
+ 4. No unnecessary invalidation when data is unchanged
71
+ 5. Every relevant sibling reader and writer was inspected
72
+
73
+ ## Reference Files
74
+
75
+ - `references/hot-path-rules.md` — Read amplification, invalidation, denormalization, indexes, digest tables
76
+ - `references/occ-conflicts.md` — Write contention, OCC resolution, hot document splitting
77
+ - `references/subscription-cost.md` — Reactive query cost, subscription granularity, point-in-time reads
78
+ - `references/function-budget.md` — Execution limits, transaction size, large documents, payload size
79
+
80
+ Also see [Convex Best Practices](https://docs.convex.dev/understanding/best-practices/).
@@ -0,0 +1,176 @@
1
+ # Convex Quickstart
2
+
3
+ Set up a working Convex project as fast as possible.
4
+
5
+ ## Scaffolding Templates
6
+
7
+ Use `npm create convex@latest` for new projects. Pass project name and template to avoid interactive prompts:
8
+
9
+ ```bash
10
+ npm create convex@latest my-app -- -t react-vite-shadcn
11
+ cd my-app
12
+ npm install
13
+ ```
14
+
15
+ | Template | Stack |
16
+ |----------|-------|
17
+ | `react-vite-shadcn` | React + Vite + Tailwind + shadcn/ui |
18
+ | `nextjs-shadcn` | Next.js App Router + Tailwind + shadcn/ui |
19
+ | `react-vite-clerk-shadcn` | React + Vite + Clerk auth + shadcn/ui |
20
+ | `nextjs-clerk` | Next.js + Clerk auth |
21
+ | `nextjs-convexauth-shadcn` | Next.js + Convex Auth + shadcn/ui |
22
+ | `bare` | Convex backend only, no frontend |
23
+
24
+ Default: `react-vite-shadcn` for simple apps, `nextjs-shadcn` for apps needing SSR or API routes.
25
+
26
+ You can use any GitHub repo as a template:
27
+
28
+ ```bash
29
+ npm create convex@latest my-app -- -t owner/repo
30
+ npm create convex@latest my-app -- -t owner/repo#branch
31
+ ```
32
+
33
+ ## Adding Convex to an Existing App
34
+
35
+ ```bash
36
+ npm install convex
37
+ ```
38
+
39
+ Then ask the user to run `npx convex dev` in their terminal.
40
+
41
+ ## ConvexProvider Wiring
42
+
43
+ Create the `ConvexReactClient` at module scope, not inside a component:
44
+
45
+ ```tsx
46
+ // Bad: re-creates the client on every render
47
+ function App() {
48
+ const convex = new ConvexReactClient(import.meta.env.VITE_CONVEX_URL as string);
49
+ return <ConvexProvider client={convex}>...</ConvexProvider>;
50
+ }
51
+
52
+ // Good: created once at module scope
53
+ const convex = new ConvexReactClient(import.meta.env.VITE_CONVEX_URL as string);
54
+ function App() {
55
+ return <ConvexProvider client={convex}>...</ConvexProvider>;
56
+ }
57
+ ```
58
+
59
+ #### React (Vite)
60
+
61
+ ```tsx
62
+ // src/main.tsx
63
+ import { ConvexProvider, ConvexReactClient } from "convex/react";
64
+
65
+ const convex = new ConvexReactClient(import.meta.env.VITE_CONVEX_URL as string);
66
+
67
+ createRoot(document.getElementById("root")!).render(
68
+ <StrictMode>
69
+ <ConvexProvider client={convex}>
70
+ <App />
71
+ </ConvexProvider>
72
+ </StrictMode>,
73
+ );
74
+ ```
75
+
76
+ #### Next.js (App Router)
77
+
78
+ ```tsx
79
+ // app/ConvexClientProvider.tsx
80
+ "use client";
81
+ import { ConvexProvider, ConvexReactClient } from "convex/react";
82
+
83
+ const convex = new ConvexReactClient(process.env.NEXT_PUBLIC_CONVEX_URL!);
84
+
85
+ export function ConvexClientProvider({ children }: { children: ReactNode }) {
86
+ return <ConvexProvider client={convex}>{children}</ConvexProvider>;
87
+ }
88
+ ```
89
+
90
+ ## Environment Variables
91
+
92
+ | Framework | Variable |
93
+ |-----------|----------|
94
+ | Vite | `VITE_CONVEX_URL` |
95
+ | Next.js | `NEXT_PUBLIC_CONVEX_URL` |
96
+ | Remix | `CONVEX_URL` |
97
+ | React Native | `EXPO_PUBLIC_CONVEX_URL` |
98
+
99
+ `npx convex dev` writes the correct variable to `.env.local` automatically.
100
+
101
+ ## Agent Mode (Cloud/Headless)
102
+
103
+ Set `CONVEX_AGENT_MODE=anonymous` in `.env.local` to run a local anonymous backend without interactive browser login:
104
+
105
+ ```bash
106
+ CONVEX_AGENT_MODE=anonymous npx convex dev
107
+ ```
108
+
109
+ ## The Dev Loop
110
+
111
+ `npx convex dev` is a long-running watcher — it's interactive on first run (browser-based OAuth). **Ask the user to run this themselves.** Once running it:
112
+ - Creates a Convex project and dev deployment
113
+ - Writes the deployment URL to `.env.local`
114
+ - Creates `convex/_generated/` with types
115
+ - Watches for changes and syncs continuously
116
+
117
+ Deploy to production separately:
118
+
119
+ ```bash
120
+ npx convex deploy
121
+ ```
122
+
123
+ ## First Function: Verification Round-Trip
124
+
125
+ `convex/schema.ts`:
126
+
127
+ ```ts
128
+ import { defineSchema, defineTable } from "convex/server";
129
+ import { v } from "convex/values";
130
+
131
+ export default defineSchema({
132
+ tasks: defineTable({
133
+ text: v.string(),
134
+ completed: v.boolean(),
135
+ }),
136
+ });
137
+ ```
138
+
139
+ `convex/tasks.ts`:
140
+
141
+ ```ts
142
+ import { query, mutation } from "./_generated/server";
143
+ import { v } from "convex/values";
144
+
145
+ export const list = query({
146
+ args: {},
147
+ handler: async (ctx) => {
148
+ return await ctx.db.query("tasks").collect();
149
+ },
150
+ });
151
+
152
+ export const create = mutation({
153
+ args: { text: v.string() },
154
+ returns: v.null(),
155
+ handler: async (ctx, args) => {
156
+ await ctx.db.insert("tasks", { text: args.text, completed: false });
157
+ return null;
158
+ },
159
+ });
160
+ ```
161
+
162
+ ## Other Frameworks
163
+
164
+ - [Vue](https://docs.convex.dev/quickstart/vue)
165
+ - [Svelte](https://docs.convex.dev/quickstart/svelte)
166
+ - [React Native](https://docs.convex.dev/quickstart/react-native)
167
+ - [TanStack Start](https://docs.convex.dev/quickstart/tanstack-start)
168
+ - [Remix](https://docs.convex.dev/quickstart/remix)
169
+ - [Node.js](https://docs.convex.dev/quickstart/nodejs)
170
+
171
+ ## Next Steps
172
+
173
+ - Add authentication → `references/auth-setup.md`
174
+ - Schema migrations → `references/migrations.md`
175
+ - Performance optimization → `references/performance-audit.md`
176
+ - Component creation → `references/components.md`