kitcn 0.0.1 → 0.12.1

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 (93) hide show
  1. package/bin/intent.js +3 -0
  2. package/dist/aggregate/index.d.ts +388 -0
  3. package/dist/aggregate/index.js +37 -0
  4. package/dist/api-entry-BckXqaLb.js +66 -0
  5. package/dist/auth/client/index.d.ts +37 -0
  6. package/dist/auth/client/index.js +217 -0
  7. package/dist/auth/config/index.d.ts +45 -0
  8. package/dist/auth/config/index.js +24 -0
  9. package/dist/auth/generated/index.d.ts +2 -0
  10. package/dist/auth/generated/index.js +3 -0
  11. package/dist/auth/http/index.d.ts +64 -0
  12. package/dist/auth/http/index.js +461 -0
  13. package/dist/auth/index.d.ts +221 -0
  14. package/dist/auth/index.js +1398 -0
  15. package/dist/auth/nextjs/index.d.ts +50 -0
  16. package/dist/auth/nextjs/index.js +81 -0
  17. package/dist/auth-store-Cljlmdmi.js +197 -0
  18. package/dist/builder-CBdG5W6A.js +1974 -0
  19. package/dist/caller-factory-cTXNvYdz.js +216 -0
  20. package/dist/cli.mjs +13264 -0
  21. package/dist/codegen-lF80HSWu.mjs +3416 -0
  22. package/dist/context-utils-HPC5nXzx.d.ts +17 -0
  23. package/dist/create-schema-odyF4kCy.js +156 -0
  24. package/dist/create-schema-orm-DOyiNDCx.js +246 -0
  25. package/dist/crpc/index.d.ts +105 -0
  26. package/dist/crpc/index.js +169 -0
  27. package/dist/customFunctions-C0voKmtx.js +144 -0
  28. package/dist/error-BZEnI7Sq.js +41 -0
  29. package/dist/generated-contract-disabled-Cih4eITO.js +50 -0
  30. package/dist/generated-contract-disabled-D-sOFy92.d.ts +354 -0
  31. package/dist/http-types-DqJubRPJ.d.ts +292 -0
  32. package/dist/meta-utils-0Pu0Nrap.js +117 -0
  33. package/dist/middleware-BUybuv9n.d.ts +34 -0
  34. package/dist/middleware-C2qTZ3V7.js +84 -0
  35. package/dist/orm/index.d.ts +17 -0
  36. package/dist/orm/index.js +10713 -0
  37. package/dist/plugins/index.d.ts +2 -0
  38. package/dist/plugins/index.js +3 -0
  39. package/dist/procedure-caller-DtxLmGwA.d.ts +1467 -0
  40. package/dist/procedure-caller-MWcxhQDv.js +349 -0
  41. package/dist/query-context-B8o6-8kC.js +1518 -0
  42. package/dist/query-context-CFZqIvD7.d.ts +42 -0
  43. package/dist/query-options-Dw7cOyXl.js +121 -0
  44. package/dist/ratelimit/index.d.ts +269 -0
  45. package/dist/ratelimit/index.js +856 -0
  46. package/dist/ratelimit/react/index.d.ts +76 -0
  47. package/dist/ratelimit/react/index.js +183 -0
  48. package/dist/react/index.d.ts +1284 -0
  49. package/dist/react/index.js +2526 -0
  50. package/dist/rsc/index.d.ts +276 -0
  51. package/dist/rsc/index.js +233 -0
  52. package/dist/runtime-CtvJPkur.js +2453 -0
  53. package/dist/server/index.d.ts +5 -0
  54. package/dist/server/index.js +6 -0
  55. package/dist/solid/index.d.ts +1221 -0
  56. package/dist/solid/index.js +2940 -0
  57. package/dist/transformer-DtDhR3Lc.js +194 -0
  58. package/dist/types-BTb_4BaU.d.ts +42 -0
  59. package/dist/types-BiJE7qxR.d.ts +4 -0
  60. package/dist/types-DEJpkIhw.d.ts +88 -0
  61. package/dist/types-HhO_R6pd.d.ts +213 -0
  62. package/dist/validators-B7oIJCAp.js +279 -0
  63. package/dist/validators-vzRKjBJC.d.ts +88 -0
  64. package/dist/watcher.mjs +96 -0
  65. package/dist/where-clause-compiler-DdjN63Io.d.ts +4756 -0
  66. package/package.json +107 -34
  67. package/skills/convex/SKILL.md +486 -0
  68. package/skills/convex/references/features/aggregates.md +353 -0
  69. package/skills/convex/references/features/auth-admin.md +446 -0
  70. package/skills/convex/references/features/auth-organizations.md +1141 -0
  71. package/skills/convex/references/features/auth-polar.md +579 -0
  72. package/skills/convex/references/features/auth.md +470 -0
  73. package/skills/convex/references/features/create-plugins.md +153 -0
  74. package/skills/convex/references/features/http.md +676 -0
  75. package/skills/convex/references/features/migrations.md +162 -0
  76. package/skills/convex/references/features/orm.md +1166 -0
  77. package/skills/convex/references/features/react.md +657 -0
  78. package/skills/convex/references/features/scheduling.md +267 -0
  79. package/skills/convex/references/features/testing.md +209 -0
  80. package/skills/convex/references/setup/auth.md +501 -0
  81. package/skills/convex/references/setup/biome.md +190 -0
  82. package/skills/convex/references/setup/doc-guidelines.md +145 -0
  83. package/skills/convex/references/setup/index.md +761 -0
  84. package/skills/convex/references/setup/next.md +116 -0
  85. package/skills/convex/references/setup/react.md +175 -0
  86. package/skills/convex/references/setup/server.md +473 -0
  87. package/skills/convex/references/setup/start.md +67 -0
  88. package/LICENSE +0 -21
  89. package/README.md +0 -0
  90. package/dist/index.d.mts +0 -5
  91. package/dist/index.d.mts.map +0 -1
  92. package/dist/index.mjs +0 -6
  93. package/dist/index.mjs.map +0 -1
@@ -0,0 +1,267 @@
1
+ # Scheduling
2
+
3
+ > Prerequisites: `setup/server.md`
4
+
5
+ Cron jobs and scheduled functions for background processing in Convex. Basics → SKILL.md Section 10. This file adds cron expressions, handler templates, job status API, error handling detail.
6
+
7
+ ## Overview
8
+
9
+ | Type | Use For |
10
+ |------|---------|
11
+ | Cron jobs | Recurring tasks on a fixed schedule |
12
+ | Scheduled functions | One-time delayed execution |
13
+
14
+ View scheduled jobs in [Dashboard](https://dashboard.convex.dev) → **Schedules** tab.
15
+
16
+ ### When to Use
17
+
18
+ | Scenario | Cron Jobs | Scheduled Functions |
19
+ |----------|-----------|---------------------|
20
+ | Daily cleanup | Fixed schedule | |
21
+ | Send email after signup | | `caller.schedule.now.*` |
22
+ | Subscription expiration | | `caller.schedule.at(timestamp).*` |
23
+ | Hourly analytics | Fixed schedule | |
24
+ | Reminder notifications | | User-defined time |
25
+ | Order processing delay | | `caller.schedule.after(5000).*` |
26
+
27
+ **Tip:** Use `caller.schedule.now.*` to trigger work immediately after a mutation commits.
28
+
29
+ ## Cron Jobs
30
+
31
+ ### Setup
32
+
33
+ ```ts
34
+ // convex/functions/crons.ts
35
+ import { cronJobs } from 'convex/server';
36
+ import { internal } from './_generated/api';
37
+
38
+ const crons = cronJobs();
39
+
40
+ // Run every 2 hours
41
+ crons.interval('cleanup stale data', { hours: 2 }, internal.crons.cleanupStaleData, {});
42
+
43
+ // Run at specific times using cron syntax
44
+ crons.cron('daily report', '0 9 * * *', internal.crons.generateDailyReport, {});
45
+
46
+ export default crons;
47
+ ```
48
+
49
+ **Note:** Always import `internal` from `./_generated/api`, even for functions in the same file.
50
+
51
+ ### Cron Expressions
52
+
53
+ | Pattern | Description |
54
+ |---------|-------------|
55
+ | `* * * * *` | Every minute |
56
+ | `*/15 * * * *` | Every 15 minutes |
57
+ | `0 * * * *` | Every hour |
58
+ | `0 0 * * *` | Daily at midnight |
59
+ | `0 9 * * *` | Daily at 9 AM |
60
+ | `0 9 * * 1-5` | Weekdays at 9 AM |
61
+ | `0 0 1 * *` | First day of month |
62
+
63
+ Format: `minute hour day-of-month month day-of-week`. Runs in **UTC**. Minimum interval is 1 minute.
64
+
65
+ ### Handler Implementation
66
+
67
+ ```ts
68
+ // convex/functions/crons.ts
69
+ import { z } from 'zod';
70
+ import { privateMutation, privateAction } from '../lib/crpc';
71
+ import { createAnalyticsCaller } from './generated/analytics.runtime';
72
+ import { createReportsCaller } from './generated/reports.runtime';
73
+
74
+ export const cleanupStaleData = privateMutation
75
+ .input(z.object({}))
76
+ .output(z.object({ deletedCount: z.number() }))
77
+ .mutation(async ({ ctx }) => {
78
+ const thirtyDaysAgo = Date.now() - 30 * 24 * 60 * 60 * 1000;
79
+ const staleSessions = await ctx.orm.query.session.findMany({
80
+ where: { lastActiveAt: { lt: thirtyDaysAgo } },
81
+ limit: 1000,
82
+ });
83
+ for (const sessionRow of staleSessions) {
84
+ await ctx.orm.delete(session).where(eq(session.id, sessionRow.id));
85
+ }
86
+ return { deletedCount: staleSessions.length };
87
+ });
88
+
89
+ export const generateDailyReport = privateAction
90
+ .input(z.object({}))
91
+
92
+ .action(async ({ ctx }) => {
93
+ const analyticsCaller = createAnalyticsCaller(ctx);
94
+ const reportsCaller = createReportsCaller(ctx);
95
+ const stats = await analyticsCaller.actions.getDailyStats({});
96
+ await reportsCaller.schedule.now.create({ type: 'daily', data: stats });
97
+ return null;
98
+ });
99
+ ```
100
+
101
+ ## Scheduled Functions
102
+
103
+ ### Key Concepts
104
+
105
+ | Concept | Description |
106
+ |---------|-------------|
107
+ | Atomicity | Scheduling from mutations is atomic — if mutation fails, nothing is scheduled |
108
+ | Non-atomic in actions | Scheduled functions from actions run even if the action fails |
109
+ | Limits | Single function can schedule up to 1000 functions with 8MB total argument size |
110
+ | Auth not propagated | Pass user info as arguments if needed |
111
+ | Results retention | Available for 7 days after completion |
112
+
113
+ **Warning:** Auth context is NOT available in scheduled functions. Pass `userId` or other auth data as arguments.
114
+
115
+ Use `caller.schedule.*` when scheduling cRPC procedures. Use `ctx.scheduler.*` only when you must schedule a raw `internal.*` Convex function.
116
+
117
+ ### caller.schedule.after
118
+
119
+ Schedule after a delay (milliseconds):
120
+
121
+ ```ts
122
+ export const processOrder = authMutation
123
+ .input(z.object({ orderId: z.string() }))
124
+
125
+ .mutation(async ({ ctx, input }) => {
126
+ const caller = createOrdersCaller(ctx);
127
+ await ctx.orm.update(orders).set({ status: 'processing' }).where(eq(orders.id, input.orderId));
128
+
129
+ // Run after 5 seconds
130
+ await caller.schedule.after(5000).charge({ orderId: input.orderId });
131
+ return null;
132
+ });
133
+ ```
134
+
135
+ ### Immediate Execution
136
+
137
+ `caller.schedule.now.*` triggers work immediately after mutation commits:
138
+
139
+ ```ts
140
+ export const createItem = authMutation
141
+ .input(z.object({ name: z.string() }))
142
+ .output(z.string())
143
+ .mutation(async ({ ctx, input }) => {
144
+ const caller = createItemsCaller(ctx);
145
+ const [row] = await ctx.orm.insert(items).values({ name: input.name }).returning({ id: items.id });
146
+
147
+ // Action runs immediately after mutation commits
148
+ await caller.schedule.now.sendNotification({ itemId: row.id });
149
+ return row.id;
150
+ });
151
+ ```
152
+
153
+ ### caller.schedule.at
154
+
155
+ Schedule at a specific Unix timestamp (ms):
156
+
157
+ ```ts
158
+ export const scheduleReminder = authMutation
159
+ .input(z.object({ message: z.string(), sendAt: z.number() }))
160
+
161
+ .mutation(async ({ ctx, input }) => {
162
+ const caller = createRemindersCaller(ctx);
163
+ if (input.sendAt <= Date.now()) {
164
+ throw new CRPCError({ code: 'BAD_REQUEST', message: 'Reminder time must be in the future' });
165
+ }
166
+ await caller.schedule.at(input.sendAt).send({ message: input.message });
167
+ return null;
168
+ });
169
+ ```
170
+
171
+ ### Canceling Scheduled Functions
172
+
173
+ Store the job ID to cancel later:
174
+
175
+ ```ts
176
+ export const createSubscription = authMutation
177
+ .input(z.object({ planId: z.string() }))
178
+ .output(z.string())
179
+ .mutation(async ({ ctx, input }) => {
180
+ const caller = createSubscriptionsCaller(ctx);
181
+ // Schedule expiration in 30 days
182
+ const expirationJobId = await caller.schedule.after(30 * 24 * 60 * 60 * 1000).expire({ userId: ctx.userId });
183
+
184
+ const [row] = await ctx.orm
185
+ .insert(subscriptions)
186
+ .values({ userId: ctx.userId, planId: input.planId, expirationJobId })
187
+ .returning({ id: subscriptions.id });
188
+ return row.id;
189
+ });
190
+
191
+ export const cancelSubscription = authMutation
192
+ .input(z.object({ subscriptionId: z.string() }))
193
+
194
+ .mutation(async ({ ctx, input }) => {
195
+ const caller = createSubscriptionsCaller(ctx);
196
+ const subscription = await ctx.orm.query.subscriptions.findFirst({ where: { id: input.subscriptionId } });
197
+ if (!subscription) throw new CRPCError({ code: 'NOT_FOUND', message: 'Subscription not found' });
198
+
199
+ if (subscription.expirationJobId) {
200
+ await caller.schedule.cancel(subscription.expirationJobId);
201
+ }
202
+ await ctx.orm.delete(subscriptions).where(eq(subscriptions.id, subscription.id));
203
+ return null;
204
+ });
205
+ ```
206
+
207
+ ## Checking Status
208
+
209
+ Query `_scheduled_functions` system table:
210
+
211
+ ```ts
212
+ export const getJobStatus = publicQuery
213
+ .input(z.object({ jobId: z.string() }))
214
+ .output(z.object({
215
+ name: z.string(),
216
+ scheduledTime: z.number(),
217
+ completedTime: z.number().optional(),
218
+ state: z.object({ kind: z.enum(['pending', 'inProgress', 'success', 'failed', 'canceled']) }),
219
+ }).nullable())
220
+ .query(async ({ ctx, input }) => {
221
+ return await ctx.orm.system.get(input.jobId);
222
+ });
223
+
224
+ export const listPendingJobs = publicQuery
225
+ .input(z.object({}))
226
+ .output(z.array(z.object({ id: z.string(), name: z.string(), scheduledTime: z.number() })))
227
+ .query(async ({ ctx }) => {
228
+ const jobs = await ctx.orm.system
229
+ .query('_scheduled_functions')
230
+ .filter((q) => q.eq(q.field('state.kind'), 'pending'))
231
+ .collect();
232
+ return jobs.map(({ id, name, scheduledTime }) => ({ id, name, scheduledTime }));
233
+ });
234
+ ```
235
+
236
+ ### Job States
237
+
238
+ | State | Description |
239
+ |-------|-------------|
240
+ | `pending` | Not started yet |
241
+ | `inProgress` | Currently running (actions only) |
242
+ | `success` | Completed successfully |
243
+ | `failed` | Hit an error |
244
+ | `canceled` | Canceled via dashboard or `caller.schedule.cancel()` |
245
+
246
+ ## Error Handling
247
+
248
+ ### Mutations
249
+
250
+ - **Automatic retry** for internal Convex errors
251
+ - **Guaranteed execution** — once scheduled, executes exactly once
252
+ - **Permanent failure** only on developer errors
253
+
254
+ ### Actions
255
+
256
+ - **No automatic retry** — actions may have side effects
257
+ - **At most once** execution
258
+ - For critical actions, implement manual retry with exponential backoff
259
+
260
+ ## Best Practices
261
+
262
+ 1. **Use internal procedures/functions** — prevent external access to scheduled work
263
+ 2. **Store job IDs** — when you need to cancel scheduled functions
264
+ 3. **Check conditions** — target may be deleted before execution
265
+ 4. **Consider idempotency** — scheduled functions might run multiple times
266
+ 5. **Pass auth info** — auth not propagated, pass user data as arguments
267
+ 6. **Use `caller.schedule.now.*`** — trigger work after mutation commits
@@ -0,0 +1,209 @@
1
+ # Testing (Consumer App Focus)
2
+
3
+ Use this for testing your app features built on kitcn.
4
+ This intentionally excludes internal package parity harnesses and deep type-matrix maintenance.
5
+
6
+ What to test + practical checklist → SKILL.md Section 11.
7
+
8
+ ## Minimal Runtime Harness
9
+
10
+ ```ts
11
+ import { test, expect } from "vitest";
12
+ import schema from "../schema";
13
+ import { convexTest, runCtx } from "../setup.testing";
14
+
15
+ test("feature", async () => {
16
+ const t = convexTest(schema);
17
+ await t.run(async (baseCtx) => {
18
+ const ctx = await runCtx(baseCtx);
19
+ // test logic
20
+ });
21
+ });
22
+ ```
23
+
24
+ ## Core Runtime Scenarios
25
+
26
+ ### 1) Happy-path mutation
27
+
28
+ ```ts
29
+ test("creates project", async () => {
30
+ const t = convexTest(schema);
31
+ await t.run(async (baseCtx) => {
32
+ const ctx = await runCtx(baseCtx);
33
+
34
+ const id = await ctx.orm.insert(project).values({
35
+ name: "Launch",
36
+ ownerId: "user_123",
37
+ });
38
+
39
+ const row = await ctx.orm.query.project.findFirstOrThrow({ where: { id } });
40
+ expect(row.name).toBe("Launch");
41
+ });
42
+ });
43
+ ```
44
+
45
+ ### 2) Auth required
46
+
47
+ ```ts
48
+ test("rejects unauthenticated call", async () => {
49
+ const t = convexTest(schema);
50
+ await t.run(async (baseCtx) => {
51
+ await expect(
52
+ baseCtx.runMutation(api.project.renameProject, {
53
+ id: "proj_1",
54
+ name: "Renamed",
55
+ })
56
+ ).rejects.toThrow(/UNAUTHORIZED/);
57
+ });
58
+ });
59
+ ```
60
+
61
+ ### 3) Ownership / forbidden
62
+
63
+ ```ts
64
+ test("rejects non-owner update", async () => {
65
+ const t = convexTest(schema);
66
+ await t.run(async (baseCtx) => {
67
+ const ctx = await runCtx(baseCtx);
68
+
69
+ const id = await ctx.orm.insert(project).values({
70
+ name: "Secret",
71
+ ownerId: "owner_1",
72
+ });
73
+
74
+ await expect(
75
+ baseCtx.runMutation(api.project.renameProject, {
76
+ id,
77
+ name: "Hacked",
78
+ })
79
+ ).rejects.toThrow(/FORBIDDEN/);
80
+ });
81
+ });
82
+ ```
83
+
84
+ ### 4) Not-found path
85
+
86
+ ```ts
87
+ test("returns not found for missing row", async () => {
88
+ const t = convexTest(schema);
89
+ await t.run(async (baseCtx) => {
90
+ await expect(
91
+ baseCtx.runMutation(api.project.renameProject, {
92
+ id: "missing",
93
+ name: "x",
94
+ })
95
+ ).rejects.toThrow(/NOT_FOUND/);
96
+ });
97
+ });
98
+ ```
99
+
100
+ ### 5) Trigger side effects
101
+
102
+ ```ts
103
+ test("updating message updates thread timestamp via trigger", async () => {
104
+ const t = convexTest(schema);
105
+ await t.run(async (baseCtx) => {
106
+ const ctx = await runCtx(baseCtx);
107
+
108
+ const threadId = await ctx.orm.insert(thread).values({ title: "T1" });
109
+ const messageId = await ctx.orm.insert(message).values({
110
+ threadId,
111
+ body: "hello",
112
+ authorId: "user_1",
113
+ });
114
+
115
+ await ctx.orm
116
+ .update(message)
117
+ .set({ body: "hello again" })
118
+ .where(eq(message.id, messageId));
119
+
120
+ const threadRow = await ctx.orm.query.thread.findFirstOrThrow({
121
+ where: { id: threadId },
122
+ });
123
+ expect(threadRow.lastMessageAt).toBeTruthy();
124
+ });
125
+ });
126
+ ```
127
+
128
+ ### 6) Scheduled jobs
129
+
130
+ ```ts
131
+ import { vi } from "vitest";
132
+
133
+ test("scheduled cleanup runs", async () => {
134
+ vi.useFakeTimers();
135
+ const t = convexTest(schema);
136
+
137
+ await t.run(async (baseCtx) => {
138
+ const ctx = await runCtx(baseCtx);
139
+
140
+ const caller = createJobsCaller(ctx);
141
+ await caller.schedule.after(1000).cleanup({ orgId: "org_1" });
142
+ vi.advanceTimersByTime(1000);
143
+ await t.finishAllScheduledFunctions();
144
+
145
+ const remaining = await ctx.orm.query.tempRecords.findMany({
146
+ where: { orgId: "org_1" },
147
+ limit: 10,
148
+ });
149
+ expect(remaining.length).toBe(0);
150
+ });
151
+
152
+ vi.useRealTimers();
153
+ });
154
+ ```
155
+
156
+ ## Lightweight Type Checks (Optional)
157
+
158
+ Only keep app-facing type checks:
159
+ - procedure input/output DTOs
160
+ - key ORM return shapes used by UI
161
+
162
+ ```ts
163
+ import { expectTypeOf } from "vitest";
164
+
165
+ test("list query result shape", async () => {
166
+ const t = convexTest(schema);
167
+ await t.run(async (baseCtx) => {
168
+ const ctx = await runCtx(baseCtx);
169
+ const rows = await ctx.orm.query.project.findMany({ limit: 5 });
170
+
171
+ expectTypeOf(rows[0]).toMatchTypeOf<{
172
+ id: string;
173
+ name: string;
174
+ createdAt: number;
175
+ }>();
176
+ });
177
+ });
178
+ ```
179
+
180
+ ## Compile-Time Type Suites (Example-Parity Optional)
181
+
182
+ If you want parity with `example/convex` type hardening, add compile-time-only files:
183
+
184
+ - `convex/lib/crpc-test.ts`:
185
+ - procedure-builder type coverage (`public`, `optionalAuth`, `auth`, `private`)
186
+ - `.paginated(...)` cursor/limit type checks
187
+ - `@ts-expect-error` assertions for invalid usage
188
+ - `convex/shared/types-typecheck.ts`:
189
+ - `Select`/`Insert` alias integrity checks
190
+ - generated API input/output shape checks
191
+ - temporal field type assertions
192
+
193
+ These files are validated by `tsc`/`bun typecheck`; they are not runtime tests.
194
+
195
+ ## Keep / Drop Guidance
196
+
197
+ Keep:
198
+ - feature tests tied to user-visible behavior
199
+ - auth/rules tests
200
+ - trigger and scheduler tests
201
+ - API contract checks at app boundary
202
+
203
+ Drop:
204
+ - internal ORM parity progress tracking
205
+ - assertion counting workflows
206
+ - package-internal generic/type torture suites
207
+ - duplicate runtime snippets with same intent
208
+
209
+ → Practical test checklist: SKILL.md Section 11 (items 1–7).