kitcn 0.0.1 → 0.12.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.
- package/bin/intent.js +3 -0
- package/dist/aggregate/index.d.ts +388 -0
- package/dist/aggregate/index.js +37 -0
- package/dist/api-entry-BckXqaLb.js +66 -0
- package/dist/auth/client/index.d.ts +37 -0
- package/dist/auth/client/index.js +217 -0
- package/dist/auth/config/index.d.ts +45 -0
- package/dist/auth/config/index.js +24 -0
- package/dist/auth/generated/index.d.ts +2 -0
- package/dist/auth/generated/index.js +3 -0
- package/dist/auth/http/index.d.ts +64 -0
- package/dist/auth/http/index.js +461 -0
- package/dist/auth/index.d.ts +221 -0
- package/dist/auth/index.js +1398 -0
- package/dist/auth/nextjs/index.d.ts +50 -0
- package/dist/auth/nextjs/index.js +81 -0
- package/dist/auth-store-Cljlmdmi.js +197 -0
- package/dist/builder-CBdG5W6A.js +1974 -0
- package/dist/caller-factory-cTXNvYdz.js +216 -0
- package/dist/cli.mjs +13255 -0
- package/dist/codegen-lF80HSWu.mjs +3416 -0
- package/dist/context-utils-HPC5nXzx.d.ts +17 -0
- package/dist/create-schema-odyF4kCy.js +156 -0
- package/dist/create-schema-orm-DOyiNDCx.js +246 -0
- package/dist/crpc/index.d.ts +105 -0
- package/dist/crpc/index.js +169 -0
- package/dist/customFunctions-C0voKmtx.js +144 -0
- package/dist/error-BZEnI7Sq.js +41 -0
- package/dist/generated-contract-disabled-Cih4eITO.js +50 -0
- package/dist/generated-contract-disabled-D-sOFy92.d.ts +354 -0
- package/dist/http-types-DqJubRPJ.d.ts +292 -0
- package/dist/meta-utils-0Pu0Nrap.js +117 -0
- package/dist/middleware-BUybuv9n.d.ts +34 -0
- package/dist/middleware-C2qTZ3V7.js +84 -0
- package/dist/orm/index.d.ts +17 -0
- package/dist/orm/index.js +10713 -0
- package/dist/plugins/index.d.ts +2 -0
- package/dist/plugins/index.js +3 -0
- package/dist/procedure-caller-DtxLmGwA.d.ts +1467 -0
- package/dist/procedure-caller-MWcxhQDv.js +349 -0
- package/dist/query-context-B8o6-8kC.js +1518 -0
- package/dist/query-context-CFZqIvD7.d.ts +42 -0
- package/dist/query-options-Dw7cOyXl.js +121 -0
- package/dist/ratelimit/index.d.ts +269 -0
- package/dist/ratelimit/index.js +856 -0
- package/dist/ratelimit/react/index.d.ts +76 -0
- package/dist/ratelimit/react/index.js +183 -0
- package/dist/react/index.d.ts +1284 -0
- package/dist/react/index.js +2526 -0
- package/dist/rsc/index.d.ts +276 -0
- package/dist/rsc/index.js +233 -0
- package/dist/runtime-CtvJPkur.js +2453 -0
- package/dist/server/index.d.ts +5 -0
- package/dist/server/index.js +6 -0
- package/dist/solid/index.d.ts +1221 -0
- package/dist/solid/index.js +2940 -0
- package/dist/transformer-DtDhR3Lc.js +194 -0
- package/dist/types-BTb_4BaU.d.ts +42 -0
- package/dist/types-BiJE7qxR.d.ts +4 -0
- package/dist/types-DEJpkIhw.d.ts +88 -0
- package/dist/types-HhO_R6pd.d.ts +213 -0
- package/dist/validators-B7oIJCAp.js +279 -0
- package/dist/validators-vzRKjBJC.d.ts +88 -0
- package/dist/watcher.mjs +96 -0
- package/dist/where-clause-compiler-DdjN63Io.d.ts +4756 -0
- package/package.json +107 -35
- package/skills/convex/SKILL.md +486 -0
- package/skills/convex/references/features/aggregates.md +353 -0
- package/skills/convex/references/features/auth-admin.md +446 -0
- package/skills/convex/references/features/auth-organizations.md +1141 -0
- package/skills/convex/references/features/auth-polar.md +579 -0
- package/skills/convex/references/features/auth.md +470 -0
- package/skills/convex/references/features/create-plugins.md +153 -0
- package/skills/convex/references/features/http.md +676 -0
- package/skills/convex/references/features/migrations.md +162 -0
- package/skills/convex/references/features/orm.md +1166 -0
- package/skills/convex/references/features/react.md +657 -0
- package/skills/convex/references/features/scheduling.md +267 -0
- package/skills/convex/references/features/testing.md +209 -0
- package/skills/convex/references/setup/auth.md +501 -0
- package/skills/convex/references/setup/biome.md +190 -0
- package/skills/convex/references/setup/doc-guidelines.md +145 -0
- package/skills/convex/references/setup/index.md +759 -0
- package/skills/convex/references/setup/next.md +116 -0
- package/skills/convex/references/setup/react.md +175 -0
- package/skills/convex/references/setup/server.md +473 -0
- package/skills/convex/references/setup/start.md +67 -0
- package/LICENSE +0 -21
- package/README.md +0 -0
- package/dist/index.d.mts +0 -5
- package/dist/index.d.mts.map +0 -1
- package/dist/index.mjs +0 -6
- package/dist/index.mjs.map +0 -1
|
@@ -0,0 +1,473 @@
|
|
|
1
|
+
## 5. Core Backend
|
|
2
|
+
|
|
3
|
+
For production bootstrap, start in the CLI Registry: use `bunx kitcn init -t <next|vite> --yes` for the shortest fresh local path, `bunx kitcn init --yes` to adopt the current app and finish the first local Convex bootstrap in one command, and `bunx kitcn add <plugin>` for feature layers. This file is the manual backend wiring reference.
|
|
4
|
+
|
|
5
|
+
### 5.1 Define schema and relations
|
|
6
|
+
|
|
7
|
+
**Create:** `convex/functions/schema.ts`
|
|
8
|
+
|
|
9
|
+
```ts
|
|
10
|
+
import {
|
|
11
|
+
boolean,
|
|
12
|
+
convexTable,
|
|
13
|
+
defineSchema,
|
|
14
|
+
index,
|
|
15
|
+
text,
|
|
16
|
+
timestamp,
|
|
17
|
+
} from "kitcn/orm";
|
|
18
|
+
|
|
19
|
+
export const user = convexTable(
|
|
20
|
+
"user",
|
|
21
|
+
{
|
|
22
|
+
name: text().notNull(),
|
|
23
|
+
email: text().notNull(),
|
|
24
|
+
emailVerified: boolean().notNull(),
|
|
25
|
+
image: text(),
|
|
26
|
+
createdAt: timestamp().notNull().defaultNow(),
|
|
27
|
+
updatedAt: timestamp().notNull(),
|
|
28
|
+
role: text(),
|
|
29
|
+
banned: boolean(),
|
|
30
|
+
banReason: text(),
|
|
31
|
+
banExpires: timestamp(),
|
|
32
|
+
},
|
|
33
|
+
(t) => [index("email").on(t.email)]
|
|
34
|
+
);
|
|
35
|
+
|
|
36
|
+
export const session = convexTable(
|
|
37
|
+
"session",
|
|
38
|
+
{
|
|
39
|
+
token: text().notNull(),
|
|
40
|
+
userId: text()
|
|
41
|
+
.references(() => user.id, { onDelete: "cascade" })
|
|
42
|
+
.notNull(),
|
|
43
|
+
expiresAt: timestamp().notNull(),
|
|
44
|
+
createdAt: timestamp().notNull().defaultNow(),
|
|
45
|
+
updatedAt: timestamp().notNull(),
|
|
46
|
+
ipAddress: text(),
|
|
47
|
+
userAgent: text(),
|
|
48
|
+
impersonatedBy: text(),
|
|
49
|
+
},
|
|
50
|
+
(t) => [index("token").on(t.token), index("userId").on(t.userId)]
|
|
51
|
+
);
|
|
52
|
+
|
|
53
|
+
export const account = convexTable(
|
|
54
|
+
"account",
|
|
55
|
+
{
|
|
56
|
+
accountId: text().notNull(),
|
|
57
|
+
providerId: text().notNull(),
|
|
58
|
+
userId: text()
|
|
59
|
+
.references(() => user.id, { onDelete: "cascade" })
|
|
60
|
+
.notNull(),
|
|
61
|
+
accessToken: text(),
|
|
62
|
+
refreshToken: text(),
|
|
63
|
+
idToken: text(),
|
|
64
|
+
accessTokenExpiresAt: timestamp(),
|
|
65
|
+
refreshTokenExpiresAt: timestamp(),
|
|
66
|
+
scope: text(),
|
|
67
|
+
password: text(),
|
|
68
|
+
createdAt: timestamp().notNull().defaultNow(),
|
|
69
|
+
updatedAt: timestamp().notNull(),
|
|
70
|
+
},
|
|
71
|
+
(t) => [index("accountId").on(t.accountId), index("userId").on(t.userId)]
|
|
72
|
+
);
|
|
73
|
+
|
|
74
|
+
export const verification = convexTable(
|
|
75
|
+
"verification",
|
|
76
|
+
{
|
|
77
|
+
identifier: text().notNull(),
|
|
78
|
+
value: text().notNull(),
|
|
79
|
+
expiresAt: timestamp().notNull(),
|
|
80
|
+
createdAt: timestamp().notNull().defaultNow(),
|
|
81
|
+
updatedAt: timestamp().notNull(),
|
|
82
|
+
},
|
|
83
|
+
(t) => [index("identifier").on(t.identifier)]
|
|
84
|
+
);
|
|
85
|
+
|
|
86
|
+
export const jwks = convexTable("jwks", {
|
|
87
|
+
publicKey: text().notNull(),
|
|
88
|
+
privateKey: text().notNull(),
|
|
89
|
+
createdAt: timestamp().notNull().defaultNow(),
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
export const project = convexTable(
|
|
93
|
+
"project",
|
|
94
|
+
{
|
|
95
|
+
name: text().notNull(),
|
|
96
|
+
ownerId: text()
|
|
97
|
+
.references(() => user.id, { onDelete: "cascade" })
|
|
98
|
+
.notNull(),
|
|
99
|
+
createdAt: timestamp().notNull().defaultNow(),
|
|
100
|
+
updatedAt: timestamp().notNull(),
|
|
101
|
+
},
|
|
102
|
+
(t) => [index("ownerId_updatedAt").on(t.ownerId, t.updatedAt)]
|
|
103
|
+
);
|
|
104
|
+
|
|
105
|
+
export const tables = { user, session, account, verification, jwks, project };
|
|
106
|
+
|
|
107
|
+
export default defineSchema(tables, {
|
|
108
|
+
strict: false,
|
|
109
|
+
}).relations((r) => ({
|
|
110
|
+
user: {
|
|
111
|
+
projects: r.many.project(),
|
|
112
|
+
sessions: r.many.session(),
|
|
113
|
+
accounts: r.many.account(),
|
|
114
|
+
},
|
|
115
|
+
project: {
|
|
116
|
+
owner: r.one.user({ from: r.project.ownerId, to: r.user.id }),
|
|
117
|
+
},
|
|
118
|
+
session: {
|
|
119
|
+
user: r.one.user({ from: r.session.userId, to: r.user.id }),
|
|
120
|
+
},
|
|
121
|
+
account: {
|
|
122
|
+
user: r.one.user({ from: r.account.userId, to: r.user.id }),
|
|
123
|
+
},
|
|
124
|
+
}));
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
### 5.1.1 Reserved index fields (important)
|
|
128
|
+
|
|
129
|
+
Do not index `createdAt` directly in kitcn ORM examples.
|
|
130
|
+
`createdAt` maps to Convex internal `_creationTime`, and explicit indexes on it can fail.
|
|
131
|
+
Prefer `updatedAt` (or a dedicated sortable field) for custom index definitions.
|
|
132
|
+
|
|
133
|
+
### 5.2 Attach ORM once (`ctx.orm`)
|
|
134
|
+
|
|
135
|
+
Do **not** create `convex/lib/orm.ts`.
|
|
136
|
+
`convex/functions/generated/` directory is generated and is the canonical server contract.
|
|
137
|
+
It includes `initCRPC` (from `generated/server`) and ORM helpers when schema relations metadata exists.
|
|
138
|
+
If you are not using codegen, use manual `initCRPC` from `kitcn/server` with `.dataModel()` and optional `.context()`.
|
|
139
|
+
|
|
140
|
+
Why this shape:
|
|
141
|
+
|
|
142
|
+
1. `orm.with(ctx)` preserves query vs mutation capabilities in type space.
|
|
143
|
+
2. It avoids common setup-time type failures like missing `insert`/`update` on `ctx.orm` in mutation handlers.
|
|
144
|
+
|
|
145
|
+
### 5.3 Initialize cRPC and procedure builders
|
|
146
|
+
|
|
147
|
+
**Create:** `convex/lib/crpc.ts`
|
|
148
|
+
|
|
149
|
+
```ts
|
|
150
|
+
import { initCRPC } from "../functions/generated/server";
|
|
151
|
+
|
|
152
|
+
const c = initCRPC
|
|
153
|
+
.meta<{
|
|
154
|
+
// Reserved for auth phase; do not implement auth logic yet.
|
|
155
|
+
auth?: "optional" | "required";
|
|
156
|
+
role?: "admin";
|
|
157
|
+
ratelimit?: string;
|
|
158
|
+
}>()
|
|
159
|
+
.create();
|
|
160
|
+
|
|
161
|
+
// Phase 1 baseline: public + private only.
|
|
162
|
+
// Do not add auth-aware builders until Section 6.9 and Section 11.3 pass.
|
|
163
|
+
export const publicQuery = c.query;
|
|
164
|
+
export const publicAction = c.action;
|
|
165
|
+
export const publicMutation = c.mutation;
|
|
166
|
+
|
|
167
|
+
export const privateQuery = c.query.internal();
|
|
168
|
+
export const privateMutation = c.mutation.internal();
|
|
169
|
+
export const privateAction = c.action.internal();
|
|
170
|
+
|
|
171
|
+
export const publicRoute = c.httpAction;
|
|
172
|
+
export const router = c.router;
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
Phase ordering rule:
|
|
176
|
+
|
|
177
|
+
1. Keep this non-auth baseline until Section 11.2 fully passes.
|
|
178
|
+
2. Only then replace this file with the auth-aware variant in Section 6.9.
|
|
179
|
+
|
|
180
|
+
### 5.4 Shared API/type helpers (generated)
|
|
181
|
+
|
|
182
|
+
Do **not** create `convex/shared/api.ts` manually.
|
|
183
|
+
It is generated by `kitcn dev`.
|
|
184
|
+
|
|
185
|
+
Generated exports include:
|
|
186
|
+
|
|
187
|
+
1. `api` (typed procedure leaves + metadata)
|
|
188
|
+
2. `Api`, `ApiInputs`, `ApiOutputs`
|
|
189
|
+
3. `TableName`, `Select`, `Insert` (when `schema.ts` exports `tables`)
|
|
190
|
+
|
|
191
|
+
Consume these from `@convex/api` on app/client side.
|
|
192
|
+
Within Convex backend files, import server context/ORM helpers from `../functions/generated/server`.
|
|
193
|
+
|
|
194
|
+
### 5.5 Start dev/codegen
|
|
195
|
+
|
|
196
|
+
Run:
|
|
197
|
+
|
|
198
|
+
```bash
|
|
199
|
+
bunx kitcn dev
|
|
200
|
+
```
|
|
201
|
+
|
|
202
|
+
If this requires interactive Convex setup, let `bunx kitcn dev` drive it first. Use `bunx convex init` only when you need to resolve deployment targeting separately.
|
|
203
|
+
Do not fake generated files.
|
|
204
|
+
|
|
205
|
+
Automation/non-interactive path:
|
|
206
|
+
|
|
207
|
+
1. Run `bunx kitcn init --yes` when you want scaffold or adoption plus the one-shot local Convex bootstrap in one command.
|
|
208
|
+
2. Run `bunx kitcn verify` when you want a non-interactive local runtime proof in the current app. It reuses an existing local deployment when one is already configured, and only falls back to anonymous fresh-local setup when it has to.
|
|
209
|
+
3. Confirm the generated runtime exists in `convex/functions/generated/server.ts`.
|
|
210
|
+
4. Then run `bunx kitcn dev` for ongoing codegen/API refresh.
|
|
211
|
+
|
|
212
|
+
Local deployment storage: New local and anonymous deployments store state under `.convex/` in the project root.
|
|
213
|
+
|
|
214
|
+
This generates:
|
|
215
|
+
|
|
216
|
+
- `convex/functions/_generated/*`
|
|
217
|
+
- `convex/functions/generated/` directory
|
|
218
|
+
- `convex/shared/api.ts`
|
|
219
|
+
|
|
220
|
+
Agent command policy:
|
|
221
|
+
|
|
222
|
+
1. Default to `bunx kitcn dev`.
|
|
223
|
+
2. `kitcn dev` already runs codegen/API generation.
|
|
224
|
+
3. Do not run `bunx kitcn codegen` as a separate default step.
|
|
225
|
+
4. Use `bunx kitcn verify` for one-shot local runtime proof in CI or agent runs.
|
|
226
|
+
5. Use manual `bunx kitcn codegen` only as fallback when `kitcn dev` cannot be run and backend is already active.
|
|
227
|
+
6. Use `bunx kitcn insights` for cloud-deployment debugging; it forwards to the upstream Convex insights CLI.
|
|
228
|
+
|
|
229
|
+
One-time codegen (optional; use only when `kitcn dev` is not running):
|
|
230
|
+
|
|
231
|
+
```bash
|
|
232
|
+
bunx kitcn codegen
|
|
233
|
+
```
|
|
234
|
+
|
|
235
|
+
Codegen runtime rule:
|
|
236
|
+
|
|
237
|
+
1. `kitcn codegen` still requires a configured Convex deployment.
|
|
238
|
+
2. If you need a runtime proof without a long-running dev session, use `bunx kitcn verify`.
|
|
239
|
+
3. If you see `Local backend isn't running`, use `bunx kitcn verify` instead of hand-holding a second terminal.
|
|
240
|
+
|
|
241
|
+
### 5.6 Import rules (hard requirement)
|
|
242
|
+
|
|
243
|
+
Never use lazy imports (`await import(...)`) in Convex code.
|
|
244
|
+
|
|
245
|
+
Rules:
|
|
246
|
+
|
|
247
|
+
1. Convex files (`convex/functions/**`, `convex/lib/**`, `convex/routers/**`) must use static imports only.
|
|
248
|
+
2. If generated modules are missing (`_generated/*`, `@convex/api`), stop and run `bunx kitcn dev` first.
|
|
249
|
+
3. Do not work around missing generated files with dynamic imports.
|
|
250
|
+
|
|
251
|
+
## 9. Optional Modules Setup (Feature Gates)
|
|
252
|
+
|
|
253
|
+
Enable only selected modules.
|
|
254
|
+
|
|
255
|
+
### 9.0 Component composition rule (`convex/functions/convex.config.ts`)
|
|
256
|
+
|
|
257
|
+
When components are enabled, register them in one `defineApp()` file:
|
|
258
|
+
|
|
259
|
+
```ts
|
|
260
|
+
import { defineApp } from "convex/server";
|
|
261
|
+
import myComponent from "some-component/convex.config";
|
|
262
|
+
|
|
263
|
+
const app = defineApp();
|
|
264
|
+
|
|
265
|
+
app.use(myComponent);
|
|
266
|
+
|
|
267
|
+
export default app;
|
|
268
|
+
```
|
|
269
|
+
|
|
270
|
+
### 9.1 RLS gate
|
|
271
|
+
|
|
272
|
+
Use `rlsPolicy` on ORM tables, evaluate through ORM context:
|
|
273
|
+
|
|
274
|
+
```ts
|
|
275
|
+
import { convexTable, id, rlsPolicy, text, eq } from "kitcn/orm";
|
|
276
|
+
|
|
277
|
+
export const secret = convexTable.withRLS(
|
|
278
|
+
"secret",
|
|
279
|
+
{
|
|
280
|
+
ownerId: id("user").notNull(),
|
|
281
|
+
value: text().notNull(),
|
|
282
|
+
},
|
|
283
|
+
(t) => [
|
|
284
|
+
rlsPolicy("read_own", {
|
|
285
|
+
for: "select",
|
|
286
|
+
using: (ctx) => eq(t.ownerId, ctx.viewerId),
|
|
287
|
+
}),
|
|
288
|
+
]
|
|
289
|
+
);
|
|
290
|
+
```
|
|
291
|
+
|
|
292
|
+
### 9.2 Schema triggers gate
|
|
293
|
+
|
|
294
|
+
```ts
|
|
295
|
+
import { convexTable, defineTriggers, text } from "kitcn/orm";
|
|
296
|
+
|
|
297
|
+
const triggers = defineTriggers(relations, {
|
|
298
|
+
post: {
|
|
299
|
+
change: async (change, ctx) => {
|
|
300
|
+
if (change.operation === "delete") return;
|
|
301
|
+
// side effects here
|
|
302
|
+
},
|
|
303
|
+
},
|
|
304
|
+
});
|
|
305
|
+
```
|
|
306
|
+
|
|
307
|
+
Trigger guardrails:
|
|
308
|
+
|
|
309
|
+
1. Keep trigger work bounded and idempotent.
|
|
310
|
+
2. Avoid trigger chains that re-query/rewrite the same hot table rows during seed/init flows.
|
|
311
|
+
3. If `internal.seed.seed` or `internal.init.default` hangs, move counter/invariant sync into explicit mutation helpers and seed reconciliation.
|
|
312
|
+
|
|
313
|
+
### 9.3 Aggregates gate
|
|
314
|
+
|
|
315
|
+
Declare `aggregateIndex` and/or `rankIndex` in table definitions. Backfill runs automatically via `kitcn dev`.
|
|
316
|
+
|
|
317
|
+
```ts
|
|
318
|
+
// convex/functions/schema.ts
|
|
319
|
+
const postLikes = convexTable(
|
|
320
|
+
"postLikes",
|
|
321
|
+
{ postId: text().notNull(), userId: text().notNull() },
|
|
322
|
+
(t) => [aggregateIndex("by_post").on(t.postId)]
|
|
323
|
+
);
|
|
324
|
+
|
|
325
|
+
const scores = convexTable(
|
|
326
|
+
"scores",
|
|
327
|
+
{ gameId: text().notNull(), score: integer().notNull() },
|
|
328
|
+
(t) => [
|
|
329
|
+
rankIndex("leaderboard")
|
|
330
|
+
.partitionBy(t.gameId)
|
|
331
|
+
.orderBy({ column: t.score, direction: "desc" }),
|
|
332
|
+
]
|
|
333
|
+
);
|
|
334
|
+
```
|
|
335
|
+
|
|
336
|
+
No trigger wiring needed — `aggregateIndex` and `rankIndex` are maintained automatically by the ORM.
|
|
337
|
+
|
|
338
|
+
If Aggregates are **disabled**, remove `aggregateIndex`/`rankIndex` declarations from table definitions and re-run `bunx kitcn verify`.
|
|
339
|
+
|
|
340
|
+
### 9.4 Rate limiting gate
|
|
341
|
+
|
|
342
|
+
Use the built-in package module (no component registration):
|
|
343
|
+
|
|
344
|
+
```bash
|
|
345
|
+
bun add kitcn
|
|
346
|
+
```
|
|
347
|
+
|
|
348
|
+
`aggregateExtension` and `migrationExtension` are builtin in `defineSchema`.
|
|
349
|
+
Rate limiting is opt-in: scaffold the full starter once.
|
|
350
|
+
|
|
351
|
+
```bash
|
|
352
|
+
bunx kitcn add ratelimit
|
|
353
|
+
```
|
|
354
|
+
|
|
355
|
+
This creates `convex/lib/plugins/ratelimit/schema.ts`, `convex/lib/plugins/ratelimit/plugin.ts`, and registers `ratelimitExtension()` in `convex/functions/schema.ts`.
|
|
356
|
+
|
|
357
|
+
```ts
|
|
358
|
+
import { defineSchema } from "kitcn/orm";
|
|
359
|
+
import { ratelimitExtension } from "../lib/plugins/ratelimit/schema";
|
|
360
|
+
|
|
361
|
+
export default defineSchema(tables).extend(ratelimitExtension());
|
|
362
|
+
```
|
|
363
|
+
|
|
364
|
+
Create `convex/lib/plugins/ratelimit/plugin.ts` and call `ratelimit.middleware()` from mutation builders. Use the default bucket for normal writes and reserve `.meta({ ratelimit: ... })` for named overrides.
|
|
365
|
+
|
|
366
|
+
Use `RatelimitPlugin` from `kitcn/ratelimit`:
|
|
367
|
+
|
|
368
|
+
```ts
|
|
369
|
+
import { getSessionNetworkSignals } from "kitcn/auth";
|
|
370
|
+
import { MINUTE, Ratelimit, RatelimitPlugin } from "kitcn/ratelimit";
|
|
371
|
+
import type { MutationCtx } from "../../../functions/generated/server";
|
|
372
|
+
import type { Select } from "../../../shared/api";
|
|
373
|
+
|
|
374
|
+
const fixed = (rate: number) => Ratelimit.fixedWindow(rate, MINUTE);
|
|
375
|
+
|
|
376
|
+
export const ratelimitBuckets = {
|
|
377
|
+
default: {
|
|
378
|
+
public: fixed(30),
|
|
379
|
+
free: fixed(60),
|
|
380
|
+
premium: fixed(200),
|
|
381
|
+
},
|
|
382
|
+
} as const;
|
|
383
|
+
|
|
384
|
+
type RatelimitTier = keyof (typeof ratelimitBuckets)["default"];
|
|
385
|
+
export type RatelimitBucket = keyof typeof ratelimitBuckets;
|
|
386
|
+
|
|
387
|
+
type RatelimitUser = {
|
|
388
|
+
id: string;
|
|
389
|
+
isAdmin?: boolean;
|
|
390
|
+
plan?: "premium" | "team" | null;
|
|
391
|
+
session?: Select<"session"> | null;
|
|
392
|
+
};
|
|
393
|
+
|
|
394
|
+
type RatelimitCtx = MutationCtx & {
|
|
395
|
+
user?: RatelimitUser | null;
|
|
396
|
+
};
|
|
397
|
+
|
|
398
|
+
type RatelimitMeta = {
|
|
399
|
+
ratelimit?: RatelimitBucket;
|
|
400
|
+
};
|
|
401
|
+
|
|
402
|
+
export function getUserTier(user: RatelimitUser | null): RatelimitTier {
|
|
403
|
+
if (!user) return "public";
|
|
404
|
+
if (user.isAdmin || user.plan) return "premium";
|
|
405
|
+
return "free";
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
export const ratelimit = RatelimitPlugin.configure({
|
|
409
|
+
buckets: ratelimitBuckets,
|
|
410
|
+
getBucket: ({ meta }: { meta: RatelimitMeta }) => meta.ratelimit ?? "default",
|
|
411
|
+
getUser: ({ ctx }: { ctx: RatelimitCtx }) => ctx.user ?? null,
|
|
412
|
+
getIdentifier: ({ user }: { user: RatelimitUser | null }) =>
|
|
413
|
+
user?.id ?? "anonymous",
|
|
414
|
+
getTier: getUserTier,
|
|
415
|
+
getSignals: ({
|
|
416
|
+
ctx,
|
|
417
|
+
user,
|
|
418
|
+
}: {
|
|
419
|
+
ctx: RatelimitCtx;
|
|
420
|
+
user: RatelimitUser | null;
|
|
421
|
+
}) => getSessionNetworkSignals(ctx, user?.session ?? null),
|
|
422
|
+
prefix: ({ bucket, tier }) => `ratelimit:${bucket}:${tier}`,
|
|
423
|
+
failureMode: "closed",
|
|
424
|
+
enableProtection: true,
|
|
425
|
+
denyListThreshold: 30,
|
|
426
|
+
});
|
|
427
|
+
```
|
|
428
|
+
|
|
429
|
+
### 9.5 Scheduling gate
|
|
430
|
+
|
|
431
|
+
Create `convex/functions/crons.ts` with `cronJobs()` and use `caller.schedule.now/after/at` in mutations/actions for delayed procedure jobs (`ctx.scheduler.*` only for raw `internal.*` functions).
|
|
432
|
+
|
|
433
|
+
### 9.6 HTTP router gate
|
|
434
|
+
|
|
435
|
+
If REST endpoints are needed, add cRPC route builders and register routers in `convex/functions/http.ts`; consume via `crpc.http.*` client proxies.
|
|
436
|
+
|
|
437
|
+
### 9.7 Email + Resend gate
|
|
438
|
+
|
|
439
|
+
Install packages:
|
|
440
|
+
|
|
441
|
+
```bash
|
|
442
|
+
bun add @kitcn/resend
|
|
443
|
+
```
|
|
444
|
+
|
|
445
|
+
`kitcn add resend` scaffolds `convex/lib/plugins/resend/schema.ts` and registers `resendExtension()` in `convex/functions/schema.ts`.
|
|
446
|
+
|
|
447
|
+
If `paths.env` is missing, the add flow also bootstraps `convex/lib/get-env.ts`, writes `paths.env` into `kitcn.json`, and adds resend env fields there. The schema keeps `RESEND_API_KEY` optional for local dev flow, and the add command reminds you to set it in `convex/.env` before sending email. Webhook secret and default sender stay optional.
|
|
448
|
+
|
|
449
|
+
```ts
|
|
450
|
+
import { resendExtension } from "../lib/plugins/resend/schema";
|
|
451
|
+
|
|
452
|
+
export default defineSchema(tables).extend(resendExtension());
|
|
453
|
+
```
|
|
454
|
+
|
|
455
|
+
Scaffold resend runtime + helpers:
|
|
456
|
+
|
|
457
|
+
```bash
|
|
458
|
+
bunx kitcn add resend
|
|
459
|
+
```
|
|
460
|
+
|
|
461
|
+
Recommended files for this gate:
|
|
462
|
+
|
|
463
|
+
- `convex/functions/plugins/resend.ts`
|
|
464
|
+
- `convex/functions/plugins/email.tsx`
|
|
465
|
+
- `convex/lib/plugins/resend/plugin.ts`
|
|
466
|
+
- `convex/lib/plugins/resend/webhook.ts`
|
|
467
|
+
- `convex/lib/plugins/resend/crons.ts`
|
|
468
|
+
|
|
469
|
+
If `plugins/email.tsx` is selected, install React Email deps:
|
|
470
|
+
|
|
471
|
+
```bash
|
|
472
|
+
bun add @react-email/components @react-email/render react-email react react-dom
|
|
473
|
+
```
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
## 8.B TanStack Start
|
|
2
|
+
|
|
3
|
+
Explicit exception: current docs still use `@convex-dev/better-auth/*` helpers for TanStack Start integration.
|
|
4
|
+
|
|
5
|
+
### 8.B.1 Auth client + auth server helpers
|
|
6
|
+
|
|
7
|
+
**Create:** `src/lib/convex/auth/auth-client.ts`
|
|
8
|
+
|
|
9
|
+
```ts
|
|
10
|
+
import type { Auth } from "@convex/auth-shared";
|
|
11
|
+
import { convexClient } from "@convex-dev/better-auth/client/plugins";
|
|
12
|
+
import { adminClient, inferAdditionalFields } from "better-auth/client/plugins";
|
|
13
|
+
import { createAuthClient } from "better-auth/react";
|
|
14
|
+
|
|
15
|
+
export const authClient = createAuthClient({
|
|
16
|
+
baseURL:
|
|
17
|
+
typeof window === "undefined"
|
|
18
|
+
? (import.meta.env.VITE_SITE_URL as string | undefined)
|
|
19
|
+
: window.location.origin,
|
|
20
|
+
sessionOptions: { refetchOnWindowFocus: false },
|
|
21
|
+
plugins: [inferAdditionalFields<Auth>(), adminClient(), convexClient()],
|
|
22
|
+
});
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
**Create:** `src/lib/convex/auth/auth-server.ts`
|
|
26
|
+
|
|
27
|
+
```ts
|
|
28
|
+
import { convexBetterAuthReactStart } from "@convex-dev/better-auth/react-start";
|
|
29
|
+
|
|
30
|
+
export const {
|
|
31
|
+
handler,
|
|
32
|
+
getToken,
|
|
33
|
+
fetchAuthQuery,
|
|
34
|
+
fetchAuthMutation,
|
|
35
|
+
fetchAuthAction,
|
|
36
|
+
} = convexBetterAuthReactStart({
|
|
37
|
+
convexUrl: process.env.VITE_CONVEX_URL!,
|
|
38
|
+
convexSiteUrl: process.env.VITE_CONVEX_SITE_URL!,
|
|
39
|
+
});
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
### 8.B.2 Auth API endpoint
|
|
43
|
+
|
|
44
|
+
**Create:** `src/routes/api/auth/$.ts`
|
|
45
|
+
|
|
46
|
+
```ts
|
|
47
|
+
import { createFileRoute } from "@tanstack/react-router";
|
|
48
|
+
import { handler } from "@/lib/convex/auth/auth-server";
|
|
49
|
+
|
|
50
|
+
export const Route = createFileRoute("/api/auth/$")({
|
|
51
|
+
server: {
|
|
52
|
+
handlers: {
|
|
53
|
+
GET: ({ request }) => handler(request),
|
|
54
|
+
POST: ({ request }) => handler(request),
|
|
55
|
+
},
|
|
56
|
+
},
|
|
57
|
+
});
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
### 8.B.3 Caller/context and providers
|
|
61
|
+
|
|
62
|
+
Use docs pattern from `tanstack-start.mdx` for:
|
|
63
|
+
|
|
64
|
+
- `createCallerFactory` + `runServerCall`
|
|
65
|
+
- router context values (`convex`, `queryClient`, `convexQueryClient`)
|
|
66
|
+
- provider wrapping with `ConvexAuthProvider` and `initialToken`
|
|
67
|
+
|
package/LICENSE
DELETED
|
@@ -1,21 +0,0 @@
|
|
|
1
|
-
The MIT License (MIT)
|
|
2
|
-
|
|
3
|
-
zbeyens
|
|
4
|
-
|
|
5
|
-
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
-
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
-
in the Software without restriction, including without limitation the rights
|
|
8
|
-
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
-
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
-
furnished to do so, subject to the following conditions:
|
|
11
|
-
|
|
12
|
-
The above copyright notice and this permission notice shall be included in
|
|
13
|
-
all copies or substantial portions of the Software.
|
|
14
|
-
|
|
15
|
-
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
-
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
-
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
-
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
-
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
-
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
21
|
-
THE SOFTWARE.
|
package/README.md
DELETED
|
File without changes
|
package/dist/index.d.mts
DELETED
package/dist/index.d.mts.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.mts","names":[],"sources":["../src/index.ts"],"sourcesContent":[],"mappings":";cAAa"}
|
package/dist/index.mjs
DELETED
package/dist/index.mjs.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"index.mjs","names":[],"sources":["../src/index.ts"],"sourcesContent":["export const hello = () => 'hello';\n"],"mappings":";AAAA,MAAa,cAAc"}
|