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,114 @@
1
+ # WorkOS AuthKit
2
+
3
+ Official docs:
4
+
5
+ - https://docs.convex.dev/auth/authkit/
6
+ - https://docs.convex.dev/auth/authkit/add-to-app
7
+ - https://docs.convex.dev/auth/authkit/auto-provision
8
+
9
+ Use this when the app already uses WorkOS or the user wants AuthKit specifically.
10
+
11
+ ## Workflow
12
+
13
+ 1. Confirm the user wants WorkOS AuthKit
14
+ 2. Determine whether they want:
15
+ - a Convex-managed WorkOS team
16
+ - an existing WorkOS team
17
+ 3. Ask whether the user wants local-only setup or production-ready setup now
18
+ 4. Read the official Convex and WorkOS AuthKit guide
19
+ 5. Create or update `convex.json` for the app's framework and real local port
20
+ 6. Follow the correct branch of the setup flow based on that choice
21
+ 7. Configure the required WorkOS environment variables
22
+ 8. Configure `convex/auth.config.ts` for WorkOS-issued JWTs
23
+ 9. Wire the client provider and callback flow
24
+ 10. Verify authenticated requests reach Convex
25
+ 11. If the user wants production-ready setup, make sure the production WorkOS configuration is covered too
26
+ 12. Only add `storeUser` or a `users` table if the app needs first-class user rows inside Convex
27
+
28
+ ## What To Do
29
+
30
+ - Read the official Convex and WorkOS AuthKit guide before writing setup code
31
+ - Determine whether the user wants a Convex-managed WorkOS team or an existing WorkOS team
32
+ - Treat `convex.json` as a first-class part of the AuthKit setup, not an optional extra
33
+ - Follow the current setup flow from the docs instead of relying on older examples
34
+
35
+ ## Key Setup Areas
36
+
37
+ - package installation for the app's framework
38
+ - `convex.json` with the `authKit` section for dev, and preview or prod if needed
39
+ - environment variables such as `WORKOS_CLIENT_ID`, `WORKOS_API_KEY`, and redirect configuration
40
+ - `convex/auth.config.ts` wiring for WorkOS-issued JWTs
41
+ - client provider setup and token flow into Convex
42
+ - login callback and redirect configuration
43
+
44
+ ## Files and Env Vars To Expect
45
+
46
+ - `convex.json`
47
+ - `convex/auth.config.ts`
48
+ - frontend auth provider wiring
49
+ - callback or redirect route setup where the framework requires it
50
+ - WorkOS environment variables commonly include:
51
+ - `WORKOS_CLIENT_ID`
52
+ - `WORKOS_API_KEY`
53
+ - `WORKOS_COOKIE_PASSWORD`
54
+ - `VITE_WORKOS_CLIENT_ID`
55
+ - `VITE_WORKOS_REDIRECT_URI`
56
+ - `NEXT_PUBLIC_WORKOS_REDIRECT_URI`
57
+
58
+ For a managed WorkOS team, `convex dev` can provision the AuthKit environment and write local env vars such as `VITE_WORKOS_CLIENT_ID` and `VITE_WORKOS_REDIRECT_URI` into `.env.local` for Vite apps.
59
+
60
+ ## Concrete Steps
61
+
62
+ 1. Choose Convex-managed or existing WorkOS team
63
+ 2. Create or update `convex.json` with the `authKit` section for the framework in use
64
+ 3. Make sure the dev `redirectUris`, `appHomepageUrl`, `corsOrigins`, and local redirect env vars match the app's actual local port
65
+ 4. For a managed WorkOS team, run `npx convex dev` and follow the interactive onboarding flow
66
+ 5. For an existing WorkOS team, get `WORKOS_CLIENT_ID` and `WORKOS_API_KEY` from the WorkOS dashboard and set them with `npx convex env set`
67
+ 6. Create or update `convex/auth.config.ts` for WorkOS JWT validation
68
+ 7. Run the normal Convex dev or deploy flow so backend config is synced
69
+ 8. Wire the WorkOS client provider in the app
70
+ 9. Configure callback and redirect handling
71
+ 10. Verify the user can sign in and return to the app
72
+ 11. Verify Convex sees the authenticated user after login
73
+ 12. If the user wants production-ready setup, configure the production client ID, API key, redirect URI, and deployment settings too
74
+
75
+ ## Gotchas
76
+
77
+ - The docs split setup between Convex-managed and existing WorkOS teams, so ask which path the user wants if it is not obvious
78
+ - Keep dev and prod WorkOS configuration separate where the docs call for different client IDs or API keys
79
+ - Only add `storeUser` or a `users` table if the app needs first-class user rows inside Convex
80
+ - Do not mix dev and prod WorkOS credentials or redirect URIs
81
+ - If the repo already contains WorkOS setup, preserve the current tenant model unless the user wants to change it
82
+ - For managed WorkOS setup, `convex dev` is interactive the first time. In non-interactive terminals, stop and ask the user to complete the onboarding prompts.
83
+ - `convex.json` is not optional for the managed AuthKit flow. It drives redirect URI, homepage URL, CORS configuration, and local env var generation.
84
+ - If the frontend starts on a different port than the one in `convex.json`, the hosted WorkOS sign-in flow will point to the wrong callback URL. Update `convex.json`, update the local redirect env var, and run `npx convex dev` again.
85
+ - Vite can fall off `5173` if other apps are already running. Do not assume the default port still matches the generated AuthKit config.
86
+ - A successful WorkOS sign-in should redirect back to the local callback route and then reach a Convex-authenticated state. Do not stop at "the hosted WorkOS page loaded."
87
+
88
+ ## Production
89
+
90
+ - Ask whether the user wants dev-only setup or production-ready setup
91
+ - If the answer is production-ready, make sure the production WorkOS client ID, API key, redirect URI, and Convex deployment config are all covered
92
+ - Verify the production redirect and callback settings before calling the task complete
93
+ - Do not silently write a notes file into the repo by default. If the user wants rollout or handoff docs, create one explicitly.
94
+
95
+ ## Validation
96
+
97
+ - Verify the user can complete the login flow and return to the app
98
+ - Verify the callback URL matches the real frontend port in local dev
99
+ - Verify Convex receives authenticated requests after login
100
+ - Verify `convex.json` matches the framework and chosen WorkOS setup path
101
+ - Verify `convex/auth.config.ts` matches the chosen WorkOS setup path
102
+ - Verify environment variables differ correctly between local and production where needed
103
+ - If production-ready setup was requested, verify the production WorkOS configuration is also covered
104
+
105
+ ## Checklist
106
+
107
+ - [ ] Confirm the user wants WorkOS AuthKit
108
+ - [ ] Ask whether the user wants local-only setup or production-ready setup
109
+ - [ ] Choose Convex-managed or existing WorkOS team
110
+ - [ ] Create or update `convex.json`
111
+ - [ ] Configure WorkOS environment variables
112
+ - [ ] Configure `convex/auth.config.ts`
113
+ - [ ] Verify authenticated requests reach Convex after login
114
+ - [ ] If requested, configure the production deployment too
@@ -0,0 +1,134 @@
1
+ # Advanced Component Patterns
2
+
3
+ Additional patterns for Convex components that go beyond the basics covered in the main skill file.
4
+
5
+ ## Function Handles for callbacks
6
+
7
+ When the app needs to pass a callback function to the component, use function handles. This is common for components that run app-defined logic on a schedule or in a workflow.
8
+
9
+ ```ts
10
+ // App side: create a handle and pass it to the component
11
+ import { createFunctionHandle } from "convex/server";
12
+
13
+ export const startJob = mutation({
14
+ handler: async (ctx) => {
15
+ const handle = await createFunctionHandle(internal.myModule.processItem);
16
+ await ctx.runMutation(components.workpool.enqueue, {
17
+ callback: handle,
18
+ });
19
+ },
20
+ });
21
+ ```
22
+
23
+ ```ts
24
+ // Component side: accept and invoke the handle
25
+ import { v } from "convex/values";
26
+ import type { FunctionHandle } from "convex/server";
27
+ import { mutation } from "./_generated/server.js";
28
+
29
+ export const enqueue = mutation({
30
+ args: { callback: v.string() },
31
+ handler: async (ctx, args) => {
32
+ const handle = args.callback as FunctionHandle<"mutation">;
33
+ await ctx.scheduler.runAfter(0, handle, {});
34
+ },
35
+ });
36
+ ```
37
+
38
+ ## Deriving validators from schema
39
+
40
+ Instead of manually repeating field types in return validators, extend the schema validator:
41
+
42
+ ```ts
43
+ import { v } from "convex/values";
44
+ import schema from "./schema.js";
45
+
46
+ const notificationDoc = schema.tables.notifications.validator.extend({
47
+ _id: v.id("notifications"),
48
+ _creationTime: v.number(),
49
+ });
50
+
51
+ export const getLatest = query({
52
+ args: {},
53
+ returns: v.nullable(notificationDoc),
54
+ handler: async (ctx) => {
55
+ return await ctx.db.query("notifications").order("desc").first();
56
+ },
57
+ });
58
+ ```
59
+
60
+ ## Static configuration with a globals table
61
+
62
+ A common pattern for component configuration is a single-document "globals" table:
63
+
64
+ ```ts
65
+ // schema.ts
66
+ export default defineSchema({
67
+ globals: defineTable({
68
+ maxRetries: v.number(),
69
+ webhookUrl: v.optional(v.string()),
70
+ }),
71
+ // ... other tables
72
+ });
73
+ ```
74
+
75
+ ```ts
76
+ // lib.ts
77
+ export const configure = mutation({
78
+ args: { maxRetries: v.number(), webhookUrl: v.optional(v.string()) },
79
+ returns: v.null(),
80
+ handler: async (ctx, args) => {
81
+ const existing = await ctx.db.query("globals").first();
82
+ if (existing) {
83
+ await ctx.db.patch(existing._id, args);
84
+ } else {
85
+ await ctx.db.insert("globals", args);
86
+ }
87
+ return null;
88
+ },
89
+ });
90
+ ```
91
+
92
+ ## Class-based client wrappers
93
+
94
+ For components with many functions or configuration options, a class-based client provides a cleaner API. This pattern is common in published components.
95
+
96
+ ```ts
97
+ // src/client/index.ts
98
+ import type { GenericMutationCtx, GenericDataModel } from "convex/server";
99
+ import type { ComponentApi } from "../component/_generated/component.js";
100
+
101
+ type MutationCtx = Pick<GenericMutationCtx<GenericDataModel>, "runMutation">;
102
+
103
+ export class Notifications {
104
+ constructor(
105
+ private component: ComponentApi,
106
+ private options?: { defaultChannel?: string },
107
+ ) {}
108
+
109
+ async send(ctx: MutationCtx, args: { userId: string; message: string }) {
110
+ return await ctx.runMutation(this.component.lib.send, {
111
+ ...args,
112
+ channel: this.options?.defaultChannel ?? "default",
113
+ });
114
+ }
115
+ }
116
+ ```
117
+
118
+ ```ts
119
+ // App usage
120
+ import { Notifications } from "@convex-dev/notifications";
121
+ import { components } from "./_generated/api";
122
+
123
+ const notifications = new Notifications(components.notifications, {
124
+ defaultChannel: "alerts",
125
+ });
126
+
127
+ export const send = mutation({
128
+ args: { message: v.string() },
129
+ handler: async (ctx, args) => {
130
+ const userId = await getAuthUserId(ctx);
131
+ await notifications.send(ctx, { userId, message: args.message });
132
+ },
133
+ });
134
+ ```
@@ -0,0 +1,171 @@
1
+ # Convex Component Creation
2
+
3
+ Create reusable Convex components with clear boundaries and a small app-facing API.
4
+
5
+ ## Core Concepts
6
+
7
+ Components are isolated units of backend logic with their own tables and functions. They must be justified — prefer normal app code if the feature doesn't need isolated tables or reusable persistent state.
8
+
9
+ **Golden Rule — Top-Down Execution:** Data and context always flow from app to component, never from component to app. The component cannot access `ctx.auth`, cannot read `process.env`, and cannot call app functions.
10
+
11
+ ## Architecture Choice
12
+
13
+ | Goal | Shape | Reference |
14
+ |------|-------|-----------|
15
+ | Component for this app only | Local | Default |
16
+ | Publish or share across apps | Packaged | `references/components-advanced.md` |
17
+ | Not sure | Default to local | — |
18
+
19
+ Default: put under `convex/components/<componentName>/`
20
+
21
+ ## Component Skeleton
22
+
23
+ ```ts
24
+ // convex/components/notifications/convex.config.ts
25
+ import { defineComponent } from "convex/server";
26
+ export default defineComponent("notifications");
27
+ ```
28
+
29
+ ```ts
30
+ // convex/components/notifications/schema.ts
31
+ export default defineSchema({
32
+ notifications: defineTable({
33
+ userId: v.string(),
34
+ message: v.string(),
35
+ read: v.boolean(),
36
+ }).index("by_user", ["userId"]),
37
+ });
38
+ ```
39
+
40
+ ```ts
41
+ // convex/components/notifications/lib.ts
42
+ import { mutation, query } from "./_generated/server.js";
43
+
44
+ export const send = mutation({
45
+ args: { userId: v.string(), message: v.string() },
46
+ returns: v.id("notifications"),
47
+ handler: async (ctx, args) => {
48
+ return await ctx.db.insert("notifications", {
49
+ userId: args.userId,
50
+ message: args.message,
51
+ read: false,
52
+ });
53
+ },
54
+ });
55
+ ```
56
+
57
+ ```ts
58
+ // convex/convex.config.ts
59
+ import { defineApp } from "convex/server";
60
+ import notifications from "./components/notifications/convex.config.js";
61
+
62
+ const app = defineApp();
63
+ app.use(notifications);
64
+ export default app;
65
+ ```
66
+
67
+ ```ts
68
+ // convex/notifications.ts — app-side wrapper
69
+ export const sendNotification = mutation({
70
+ args: { message: v.string() },
71
+ returns: v.null(),
72
+ handler: async (ctx, args) => {
73
+ const userId = await getAuthUserId(ctx);
74
+ if (!userId) throw new Error("Not authenticated");
75
+ await ctx.runMutation(components.notifications.lib.send, {
76
+ userId,
77
+ message: args.message,
78
+ });
79
+ return null;
80
+ },
81
+ });
82
+ ```
83
+
84
+ Note: a function in `convex/components/notifications/lib.ts` is called as `components.notifications.lib.send`.
85
+
86
+ ## Critical Rules
87
+
88
+ - **Auth stays in the app** — `ctx.auth` is not available inside components. Resolve auth in the app and pass the userId.
89
+ - **Env access stays in the app** — component functions cannot read `process.env`.
90
+ - **Parent IDs cross the boundary as strings** — use `v.string()`, not `v.id("parentTable")` for app-owned tables inside component args.
91
+ - **Import from component's own generated files** — use `./_generated/server` not the app's generated files.
92
+ - **Never expose component functions directly to clients** — create app wrappers when client access is needed.
93
+ - **HTTP routes stay in the app** — if the component defines HTTP handlers, mount them in `convex/http.ts`.
94
+ - **Use `paginator` from `convex-helpers`** for pagination across component boundaries — built-in `.paginate()` doesn't work across the boundary.
95
+ - **Add `args` and `returns` validators to all public component functions** — the boundary requires explicit type contracts.
96
+
97
+ ## Key Patterns
98
+
99
+ ### Auth and Env Access
100
+
101
+ ```ts
102
+ // Good: app resolves auth and env, passes explicit values
103
+ const userId = await getAuthUserId(ctx);
104
+ if (!userId) throw new Error("Not authenticated");
105
+
106
+ await ctx.runAction(components.translator.translate, {
107
+ userId,
108
+ apiKey: process.env.OPENAI_API_KEY,
109
+ text: args.text,
110
+ });
111
+ ```
112
+
113
+ ### IDs Across the Boundary
114
+
115
+ ```ts
116
+ // Bad: parent app table IDs are not valid component validators
117
+ args: { userId: v.id("users") }
118
+
119
+ // Good: treat parent-owned IDs as strings at the boundary
120
+ args: { userId: v.string() }
121
+ ```
122
+
123
+ ### Client-Facing API
124
+
125
+ ```ts
126
+ // Bad: component function directly callable by clients
127
+ export const send = components.notifications.send;
128
+
129
+ // Good: re-export through an app mutation
130
+ export const sendNotification = mutation({
131
+ args: { message: v.string() },
132
+ handler: async (ctx, args) => {
133
+ const userId = await getAuthUserId(ctx);
134
+ if (!userId) throw new Error("Not authenticated");
135
+ await ctx.runMutation(components.notifications.lib.send, { userId, message: args.message });
136
+ return null;
137
+ },
138
+ });
139
+ ```
140
+
141
+ ## Step-by-Step Workflow
142
+
143
+ 1. Plan what tables the component owns, what public functions it exposes, what data must be passed from the app, and what stays in the app as wrappers
144
+ 2. Create `convex.config.ts`, `schema.ts`, and function files under `convex/components/<name>/`
145
+ 3. Import `query`, `mutation`, `action` from the component's own `./_generated/server`
146
+ 4. Wire into the app with `app.use(...)` in `convex/convex.config.ts`
147
+ 5. Call the component from the app through `components.<name>` using `ctx.runQuery`/`ctx.runMutation`/`ctx.runAction`
148
+ 6. Create app wrapper functions for any client access needed
149
+ 7. Run `npx convex dev` and fix codegen/type issues
150
+
151
+ ## Validation
152
+
153
+ Try validation in this order:
154
+ 1. `npx convex codegen --component-dir convex/components/<name>`
155
+ 2. `npx convex codegen`
156
+ 3. `npx convex dev`
157
+
158
+ ## Advanced Patterns
159
+
160
+ For function handles (callbacks), deriving validators from schema, globals tables, and class-based client wrappers, see `references/components-advanced.md`.
161
+
162
+ ## Checklist
163
+
164
+ - [ ] Confirmed a component is the right abstraction
165
+ - [ ] Planned tables, public API, boundaries, and app wrappers
166
+ - [ ] Component lives under `convex/components/<name>/`
167
+ - [ ] Component imports from its own `./_generated/server`
168
+ - [ ] Auth, env access, and HTTP routes stay in the app
169
+ - [ ] Parent app IDs cross the boundary as `v.string()`
170
+ - [ ] Public functions have `args` and `returns` validators
171
+ - [ ] Ran `npx convex dev` and fixed codegen or type issues
@@ -0,0 +1,232 @@
1
+ # Function Budget
2
+
3
+ Use these rules when functions are hitting execution limits, transaction size errors, or returning excessively large payloads to the client.
4
+
5
+ ## Core Principle
6
+
7
+ Convex functions run inside transactions with budgets for time, reads, and writes. Staying well within these limits is not just about avoiding errors, it reduces latency and contention.
8
+
9
+ ## Limits to Know
10
+
11
+ These are the current values from the [Convex limits docs](https://docs.convex.dev/production/state/limits). Check that page for the latest numbers.
12
+
13
+ | Resource | Limit |
14
+ |---|---|
15
+ | Query/mutation execution time | 1 second (user code only, excludes DB operations) |
16
+ | Action execution time | 10 minutes |
17
+ | Data read per transaction | 16 MiB |
18
+ | Data written per transaction | 16 MiB |
19
+ | Documents scanned per transaction | 32,000 (includes documents filtered out by `.filter`) |
20
+ | Index ranges read per transaction | 4,096 (each `db.get` and `db.query` call) |
21
+ | Documents written per transaction | 16,000 |
22
+ | Individual document size | 1 MiB |
23
+ | Function return value size | 16 MiB |
24
+
25
+ ## Symptoms
26
+
27
+ - "Function execution took too long" errors
28
+ - "Transaction too large" or read/write set size errors
29
+ - Slow queries that read many documents
30
+ - Client receiving large payloads that slow down page load
31
+ - `npx convex insights --details` showing high bytes read
32
+
33
+ ## Common Causes
34
+
35
+ ### Unbounded collection
36
+
37
+ A query that calls `.collect()` on a table without a reasonable limit. As the table grows, the query reads more and more documents.
38
+
39
+ ### Large document reads on hot paths
40
+
41
+ Reading documents with large fields (rich text, embedded media references, long arrays) when only a small subset of the data is needed for the current view.
42
+
43
+ ### Mutation doing too much work
44
+
45
+ A single mutation that updates hundreds of documents, backfills data, or rebuilds derived state in one transaction.
46
+
47
+ ### Returning too much data to the client
48
+
49
+ A query returning full documents when the client only needs a few fields.
50
+
51
+ ## Fix Order
52
+
53
+ ### 1. Bound your reads
54
+
55
+ Never `.collect()` without a limit on a table that can grow unbounded.
56
+
57
+ ```ts
58
+ // Bad: unbounded read, breaks as the table grows
59
+ const messages = await ctx.db.query("messages").collect();
60
+ ```
61
+
62
+ ```ts
63
+ // Good: paginate or limit
64
+ const messages = await ctx.db
65
+ .query("messages")
66
+ .withIndex("by_channel", (q) => q.eq("channelId", channelId))
67
+ .order("desc")
68
+ .take(50);
69
+ ```
70
+
71
+ ### 2. Read smaller shapes
72
+
73
+ If the list page only needs title, author, and date, do not read full documents with rich content fields.
74
+
75
+ Use digest or summary tables for hot list pages. See `hot-path-rules.md` for the digest table pattern.
76
+
77
+ ### 3. Break large mutations into batches
78
+
79
+ If a mutation needs to update hundreds of documents, split it into a self-scheduling chain.
80
+
81
+ ```ts
82
+ // Bad: one mutation updating every row
83
+ export const backfillAll = internalMutation({
84
+ handler: async (ctx) => {
85
+ const docs = await ctx.db.query("items").collect();
86
+ for (const doc of docs) {
87
+ await ctx.db.patch(doc._id, { newField: computeValue(doc) });
88
+ }
89
+ },
90
+ });
91
+ ```
92
+
93
+ ```ts
94
+ // Good: cursor-based batch processing
95
+ export const backfillBatch = internalMutation({
96
+ args: { cursor: v.optional(v.string()), batchSize: v.optional(v.number()) },
97
+ handler: async (ctx, args) => {
98
+ const batchSize = args.batchSize ?? 100;
99
+ const result = await ctx.db
100
+ .query("items")
101
+ .paginate({ cursor: args.cursor ?? null, numItems: batchSize });
102
+
103
+ for (const doc of result.page) {
104
+ if (doc.newField === undefined) {
105
+ await ctx.db.patch(doc._id, { newField: computeValue(doc) });
106
+ }
107
+ }
108
+
109
+ if (!result.isDone) {
110
+ await ctx.scheduler.runAfter(0, internal.items.backfillBatch, {
111
+ cursor: result.continueCursor,
112
+ batchSize,
113
+ });
114
+ }
115
+ },
116
+ });
117
+ ```
118
+
119
+ ### 4. Move heavy work to actions
120
+
121
+ Queries and mutations run inside Convex's transactional runtime with strict budgets. If you need to do CPU-intensive computation, call external APIs, or process large files, use an action instead.
122
+
123
+ Actions run outside the transaction and can call mutations to write results back.
124
+
125
+ ```ts
126
+ // Bad: heavy computation inside a mutation
127
+ export const processUpload = mutation({
128
+ handler: async (ctx, args) => {
129
+ const result = expensiveComputation(args.data);
130
+ await ctx.db.insert("results", result);
131
+ },
132
+ });
133
+ ```
134
+
135
+ ```ts
136
+ // Good: action for heavy work, mutation for the write
137
+ export const processUpload = action({
138
+ handler: async (ctx, args) => {
139
+ const result = expensiveComputation(args.data);
140
+ await ctx.runMutation(internal.results.store, { result });
141
+ },
142
+ });
143
+ ```
144
+
145
+ ### 5. Trim return values
146
+
147
+ Only return what the client needs. If a query fetches full documents but the component only renders a few fields, map the results before returning.
148
+
149
+ ```ts
150
+ // Bad: returns full documents including large content fields
151
+ export const list = query({
152
+ handler: async (ctx) => {
153
+ return await ctx.db.query("articles").take(20);
154
+ },
155
+ });
156
+ ```
157
+
158
+ ```ts
159
+ // Good: project to only the fields the client needs
160
+ export const list = query({
161
+ handler: async (ctx) => {
162
+ const articles = await ctx.db.query("articles").take(20);
163
+ return articles.map((a) => ({
164
+ _id: a._id,
165
+ title: a.title,
166
+ author: a.author,
167
+ createdAt: a._creationTime,
168
+ }));
169
+ },
170
+ });
171
+ ```
172
+
173
+ ### 6. Replace `ctx.runQuery` and `ctx.runMutation` with helper functions
174
+
175
+ Inside queries and mutations, `ctx.runQuery` and `ctx.runMutation` have overhead compared to calling a plain TypeScript helper function. They run in the same transaction but pay extra per-call cost.
176
+
177
+ ```ts
178
+ // Bad: unnecessary overhead from ctx.runQuery inside a mutation
179
+ export const createProject = mutation({
180
+ handler: async (ctx, args) => {
181
+ const user = await ctx.runQuery(api.users.getCurrentUser);
182
+ await ctx.db.insert("projects", { ...args, ownerId: user._id });
183
+ },
184
+ });
185
+ ```
186
+
187
+ ```ts
188
+ // Good: plain helper function, no extra overhead
189
+ export const createProject = mutation({
190
+ handler: async (ctx, args) => {
191
+ const user = await getCurrentUser(ctx);
192
+ await ctx.db.insert("projects", { ...args, ownerId: user._id });
193
+ },
194
+ });
195
+ ```
196
+
197
+ Exception: components require `ctx.runQuery`/`ctx.runMutation`. Use them there, but prefer helpers everywhere else.
198
+
199
+ ### 7. Avoid unnecessary `runAction` calls
200
+
201
+ `runAction` from within an action creates a separate function invocation with its own memory and CPU budget. The parent action just sits idle waiting. Replace with a plain TypeScript function call unless you need a different runtime (e.g. calling Node.js code from the Convex runtime).
202
+
203
+ ```ts
204
+ // Bad: runAction overhead for no reason
205
+ export const processItems = action({
206
+ handler: async (ctx, args) => {
207
+ for (const item of args.items) {
208
+ await ctx.runAction(internal.items.processOne, { item });
209
+ }
210
+ },
211
+ });
212
+ ```
213
+
214
+ ```ts
215
+ // Good: plain function call
216
+ export const processItems = action({
217
+ handler: async (ctx, args) => {
218
+ for (const item of args.items) {
219
+ await processOneItem(ctx, { item });
220
+ }
221
+ },
222
+ });
223
+ ```
224
+
225
+ ## Verification
226
+
227
+ 1. No function execution or transaction size errors
228
+ 2. `npx convex insights --details` shows reduced bytes read
229
+ 3. Large mutations are batched and self-scheduling
230
+ 4. Client payloads are reasonably sized for the UI they serve
231
+ 5. `ctx.runQuery`/`ctx.runMutation` in queries and mutations replaced with helpers where possible
232
+ 6. Sibling functions with similar patterns were checked