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,446 @@
1
+ # Auth Admin Plugin
2
+
3
+ Role-based admin features: middleware, user management, banning, impersonation, custom permissions.
4
+
5
+ Prerequisites: `setup/auth.md`, `setup/server.md`.
6
+
7
+ See [Better Auth Admin Plugin](https://www.better-auth.com/docs/plugins/admin) for full API reference.
8
+
9
+ ## Server Config
10
+
11
+ ```ts
12
+ // convex/functions/auth.ts
13
+ import { admin } from 'better-auth/plugins';
14
+ import { defineAuth } from './generated/auth';
15
+
16
+ export default defineAuth((ctx) => ({
17
+ plugins: [
18
+ admin({
19
+ defaultRole: 'user',
20
+ // adminUserIds: ['user_id_1'], // Always admin regardless of role
21
+ // impersonationSessionDuration: 60 * 60, // 1 hour default
22
+ // defaultBanReason: 'No reason',
23
+ // bannedUserMessage: 'You have been banned',
24
+ }),
25
+ ],
26
+ }));
27
+ ```
28
+
29
+ ### Admin Assignment via Environment
30
+
31
+ ```bash
32
+ # convex/.env
33
+ ADMIN=admin@domain.test,ops@domain.test
34
+ ```
35
+
36
+ ```ts
37
+ // convex/functions/auth.ts
38
+ import { defineAuth } from './generated/auth';
39
+
40
+ export default defineAuth((ctx) => ({
41
+ triggers: {
42
+ user: {
43
+ create: {
44
+ before: async (data, triggerCtx) => {
45
+ const env = getEnv();
46
+ const adminEmails = env.ADMIN;
47
+ const role =
48
+ data.role !== 'admin' && adminEmails?.includes(data.email)
49
+ ? 'admin'
50
+ : data.role;
51
+ return { data: { ...data, role } };
52
+ },
53
+ },
54
+ },
55
+ },
56
+ }));
57
+ ```
58
+
59
+ ## Client Config
60
+
61
+ ```ts
62
+ // src/lib/convex/auth-client.ts
63
+ import { adminClient } from 'better-auth/client/plugins';
64
+
65
+ export const authClient = createAuthClient({
66
+ plugins: [
67
+ adminClient(),
68
+ // ... other plugins
69
+ ],
70
+ });
71
+ ```
72
+
73
+ ## Schema
74
+
75
+ ```ts
76
+ // convex/functions/schema.ts
77
+ import { boolean, convexTable, defineSchema, integer, text } from 'kitcn/orm';
78
+
79
+ export const user = convexTable('user', {
80
+ // ... existing fields
81
+ role: text(), // 'admin' | 'user'
82
+ banned: boolean(),
83
+ banReason: text(),
84
+ banExpires: integer(),
85
+ });
86
+
87
+ export const session = convexTable('session', {
88
+ // ... existing fields
89
+ impersonatedBy: text(), // Admin user ID during impersonation
90
+ });
91
+
92
+ export const tables = { user, session };
93
+ export default defineSchema(tables, { strict: false });
94
+ ```
95
+
96
+ ## Access Control
97
+
98
+ ### Role Middleware
99
+
100
+ ```ts
101
+ // convex/lib/crpc.ts
102
+ const c = initCRPC
103
+ .meta<{ auth?: 'optional' | 'required'; role?: 'admin' }>()
104
+ .create();
105
+
106
+ const roleMiddleware = c.middleware<object>(({ ctx, meta, next }) => {
107
+ const user = (ctx as { user?: { role?: string | null } }).user;
108
+ if (meta.role === 'admin' && user?.role !== 'admin') {
109
+ throw new CRPCError({
110
+ code: 'FORBIDDEN',
111
+ message: 'Admin access required',
112
+ });
113
+ }
114
+ return next({ ctx });
115
+ });
116
+
117
+ export const authQuery = c.query
118
+ .meta({ auth: 'required' })
119
+ .use(devMiddleware)
120
+ .use(authMiddleware)
121
+ .use(roleMiddleware);
122
+
123
+ export const authMutation = c.mutation
124
+ .meta({ auth: 'required' })
125
+ .use(devMiddleware)
126
+ .use(authMiddleware)
127
+ .use(roleMiddleware)
128
+ .use(ratelimit.middleware());
129
+ ```
130
+
131
+ ### Role Guard Helper
132
+
133
+ ```ts
134
+ // convex/lib/auth/role-guard.ts
135
+ import { CRPCError } from 'kitcn/server';
136
+
137
+ export function roleGuard(
138
+ role: 'admin',
139
+ user: { role?: string | null } | null
140
+ ) {
141
+ if (!user) {
142
+ throw new CRPCError({ code: 'FORBIDDEN', message: 'Access denied' });
143
+ }
144
+ if (role === 'admin' && user.role !== 'admin') {
145
+ throw new CRPCError({ code: 'FORBIDDEN', message: 'Admin access required' });
146
+ }
147
+ }
148
+ ```
149
+
150
+ ### Custom Access Control
151
+
152
+ ```ts
153
+ // convex/shared/permissions.ts
154
+ import { createAccessControl } from 'better-auth/plugins/access';
155
+ import { defaultStatements, adminAc } from 'better-auth/plugins/admin/access';
156
+
157
+ const statement = {
158
+ ...defaultStatements,
159
+ project: ['create', 'read', 'update', 'delete'],
160
+ } as const;
161
+
162
+ export const ac = createAccessControl(statement);
163
+
164
+ export const admin = ac.newRole({
165
+ ...adminAc.statements,
166
+ project: ['create', 'read', 'update', 'delete'],
167
+ });
168
+
169
+ export const user = ac.newRole({
170
+ project: ['create', 'read'],
171
+ });
172
+ ```
173
+
174
+ Pass to plugins:
175
+
176
+ ```ts
177
+ // Server
178
+ admin({ ac, roles: { admin, user } })
179
+
180
+ // Client (src/lib/convex/auth-client.ts)
181
+ adminClient({ ac, roles: { admin, user } })
182
+ ```
183
+
184
+ ## Admin Functions
185
+
186
+ ### Check Admin Status
187
+
188
+ ```ts
189
+ export const checkUserAdminStatus = authQuery
190
+ .meta({ role: 'admin' })
191
+ .input(z.object({ userId: z.string() }))
192
+ .output(z.object({ isAdmin: z.boolean(), role: z.string().nullish() }))
193
+ .query(async ({ ctx, input }) => {
194
+ const user = await ctx.orm.query.user.findFirstOrThrow({ where: { id: input.userId } });
195
+ return { isAdmin: user.role === 'admin', role: user.role };
196
+ });
197
+ ```
198
+
199
+ ### Update User Role
200
+
201
+ ```ts
202
+ export const updateUserRole = authMutation
203
+ .meta({ role: 'admin' })
204
+ .input(z.object({ role: z.enum(['user', 'admin']), userId: z.string() }))
205
+ .output(z.boolean())
206
+ .mutation(async ({ ctx, input }) => {
207
+ if (input.role === 'admin' && !ctx.user.isAdmin) {
208
+ throw new CRPCError({ code: 'FORBIDDEN', message: 'Only admin can promote users to admin' });
209
+ }
210
+
211
+ const targetUser = await ctx.orm.query.user.findFirstOrThrow({ where: { id: input.userId } });
212
+
213
+ if (targetUser.role === 'admin' && !ctx.user.isAdmin) {
214
+ throw new CRPCError({ code: 'FORBIDDEN', message: 'Cannot modify admin users' });
215
+ }
216
+
217
+ await ctx.orm
218
+ .update(userTable)
219
+ .set({ role: input.role.toLowerCase() })
220
+ .where(eq(userTable.id, targetUser.id));
221
+ return true;
222
+ });
223
+ ```
224
+
225
+ ### Grant Admin by Email
226
+
227
+ ```ts
228
+ export const grantAdminByEmail = authMutation
229
+ .meta({ role: 'admin' })
230
+ .input(z.object({ email: z.string().email(), role: z.enum(['admin']) }))
231
+ .output(z.object({ success: z.boolean(), userId: z.string().optional() }))
232
+ .mutation(async ({ ctx, input }) => {
233
+ const user = await ctx.orm.query.user.findFirst({ where: { email: input.email } });
234
+ if (!user) return { success: false };
235
+
236
+ await ctx.orm
237
+ .update(userTable)
238
+ .set({ role: input.role.toLowerCase() })
239
+ .where(eq(userTable.id, user.id));
240
+ return { success: true, userId: user.id };
241
+ });
242
+ ```
243
+
244
+ ### List All Users (Paginated)
245
+
246
+ ```ts
247
+ const UserListItemSchema = z.object({
248
+ id: z.string(),
249
+ createdAt: z.date(),
250
+ name: z.string().optional(),
251
+ email: z.string(),
252
+ image: z.string().nullish(),
253
+ role: z.string(),
254
+ isBanned: z.boolean().nullish(),
255
+ banReason: z.string().nullish(),
256
+ banExpiresAt: z.date().nullish(),
257
+ });
258
+
259
+ export const getAllUsers = authQuery
260
+ .input(z.object({
261
+ role: z.enum(['all', 'user', 'admin']).optional(),
262
+ search: z.string().optional(),
263
+ }))
264
+ .paginated({ limit: 20, item: UserListItemSchema.nullable() })
265
+ .query(async ({ ctx, input }) => {
266
+ const result = await ctx.orm.query.user.findMany({
267
+ cursor: input.cursor,
268
+ limit: input.limit,
269
+ });
270
+
271
+ const enrichedPage = result.page
272
+ .map((user) => {
273
+ const userData = {
274
+ ...user,
275
+ banExpiresAt: user?.banExpires,
276
+ banReason: user?.banReason,
277
+ email: user?.email || '',
278
+ isBanned: user?.banned,
279
+ role: user?.role || 'user',
280
+ };
281
+
282
+ if (input.search) {
283
+ const searchLower = input.search.toLowerCase();
284
+ if (!(userData.name?.toLowerCase().includes(searchLower) || userData.email.toLowerCase().includes(searchLower))) {
285
+ return null;
286
+ }
287
+ }
288
+ if (input.role && input.role !== 'all' && userData.role !== input.role) return null;
289
+
290
+ return userData;
291
+ })
292
+ .filter((row): row is NonNullable<typeof row> => row !== null);
293
+
294
+ return { ...result, page: enrichedPage };
295
+ });
296
+ ```
297
+
298
+ ### Dashboard Stats
299
+
300
+ ```ts
301
+ export const getDashboardStats = authQuery
302
+ .meta({ role: 'admin' })
303
+ .output(z.object({
304
+ recentUsers: z.array(z.object({ id: z.string(), createdAt: z.date(), image: z.string().nullish(), name: z.string().optional() })),
305
+ totalAdmins: z.number(),
306
+ totalUsers: z.number(),
307
+ userGrowth: z.array(z.object({ count: z.number(), date: z.string() })),
308
+ }))
309
+ .query(async ({ ctx }) => {
310
+ const toRows = <TRow>(result: TRow[] | { page: TRow[] }): TRow[] =>
311
+ Array.isArray(result) ? result : result.page;
312
+
313
+ const recentUsers = toRows(await ctx.orm.query.user.findMany({
314
+ limit: 5, orderBy: { createdAt: 'desc' },
315
+ columns: { id: true, createdAt: true, image: true, name: true },
316
+ }));
317
+
318
+ const usersLast7Days = toRows(await ctx.orm.query.user.findMany({
319
+ where: { createdAt: { gte: new Date(Date.now() - 7 * 24 * 60 * 60 * 1000) } },
320
+ limit: 1000,
321
+ }));
322
+
323
+ const userGrowth: { count: number; date: string }[] = [];
324
+ const now = Date.now();
325
+ const oneDay = 24 * 60 * 60 * 1000;
326
+ for (let i = 6; i >= 0; i--) {
327
+ const date = new Date(now - i * oneDay);
328
+ const startOfDay = new Date(date.setHours(0, 0, 0, 0)).getTime();
329
+ const endOfDay = new Date(date.setHours(23, 59, 59, 999)).getTime();
330
+ userGrowth.push({
331
+ count: usersLast7Days.filter((u) => {
332
+ const t = u.createdAt.getTime();
333
+ return t >= startOfDay && t <= endOfDay;
334
+ }).length,
335
+ date: new Date(startOfDay).toISOString().split('T')[0],
336
+ });
337
+ }
338
+
339
+ const sampleUsers = toRows(await ctx.orm.query.user.findMany({ limit: 100 }));
340
+ const adminCount = sampleUsers.filter((u) => u.role === 'admin').length;
341
+ const totalUsers = await ctx.orm.query.user.count();
342
+ const totalAdmins = Math.round((adminCount / sampleUsers.length) * totalUsers);
343
+
344
+ return { recentUsers, totalAdmins, totalUsers, userGrowth };
345
+ });
346
+ ```
347
+
348
+ ## Client Usage
349
+
350
+ ### Check Admin Status
351
+
352
+ ```ts
353
+ const { data: session } = authClient.useSession();
354
+ const isAdmin = session?.user?.role === 'admin';
355
+ ```
356
+
357
+ ### Ban/Unban Users
358
+
359
+ ```ts
360
+ await authClient.admin.banUser({
361
+ userId: 'user_123',
362
+ banReason: 'Violation of terms',
363
+ banExpiresIn: 60 * 60 * 24 * 7, // 7 days
364
+ });
365
+
366
+ await authClient.admin.unbanUser({ userId: 'user_123' });
367
+ ```
368
+
369
+ ### Session Management
370
+
371
+ ```ts
372
+ const { data: sessions } = await authClient.admin.listUserSessions({ userId: 'user_123' });
373
+ await authClient.admin.revokeUserSession({ sessionToken: 'session_token' });
374
+ await authClient.admin.revokeUserSessions({ userId: 'user_123' }); // All sessions
375
+ ```
376
+
377
+ ### Impersonation
378
+
379
+ ```ts
380
+ await authClient.admin.impersonateUser({ userId: 'user_123' });
381
+ await authClient.admin.stopImpersonating();
382
+ ```
383
+
384
+ ### User Management
385
+
386
+ ```ts
387
+ // Create user
388
+ await authClient.admin.createUser({
389
+ email: 'user@domain.test', password: 'password', name: 'John Doe', role: 'user',
390
+ });
391
+
392
+ // List users (with filtering/sorting/pagination)
393
+ const { users, total } = await authClient.admin.listUsers({
394
+ searchValue: 'john', searchField: 'name', limit: 20, offset: 0,
395
+ sortBy: 'createdAt', sortDirection: 'desc',
396
+ });
397
+
398
+ // Set role
399
+ await authClient.admin.setRole({ userId, role: 'admin' });
400
+
401
+ // Set password
402
+ await authClient.admin.setUserPassword({ userId, newPassword });
403
+
404
+ // Update user
405
+ await authClient.admin.updateUser({ userId, data: { name: 'New Name' } });
406
+
407
+ // Delete user
408
+ await authClient.admin.removeUser({ userId });
409
+ ```
410
+
411
+ ### Permission Checking
412
+
413
+ ```ts
414
+ // Server call
415
+ const { success } = await authClient.admin.hasPermission({
416
+ permissions: { project: ['create', 'update'] },
417
+ });
418
+
419
+ // Client-side, no server call
420
+ const canDelete = authClient.admin.checkRolePermission({
421
+ role: 'admin',
422
+ permissions: { project: ['delete'] },
423
+ });
424
+ ```
425
+
426
+ ## API Reference
427
+
428
+ | Operation | Method | Admin Required |
429
+ |-----------|--------|----------------|
430
+ | Create user | `authClient.admin.createUser` | Yes |
431
+ | List users | `authClient.admin.listUsers` | Yes |
432
+ | Set role | `authClient.admin.setRole` | Yes |
433
+ | Set password | `authClient.admin.setUserPassword` | Yes |
434
+ | Update user | `authClient.admin.updateUser` | Yes |
435
+ | Ban user | `authClient.admin.banUser` | Yes |
436
+ | Unban user | `authClient.admin.unbanUser` | Yes |
437
+ | List sessions | `authClient.admin.listUserSessions` | Yes |
438
+ | Revoke session | `authClient.admin.revokeUserSession` | Yes |
439
+ | Revoke all sessions | `authClient.admin.revokeUserSessions` | Yes |
440
+ | Impersonate | `authClient.admin.impersonateUser` | Yes |
441
+ | Stop impersonating | `authClient.admin.stopImpersonating` | Yes |
442
+ | Remove user | `authClient.admin.removeUser` | Yes |
443
+ | Check permission | `authClient.admin.hasPermission` | No |
444
+ | Check role permission | `authClient.admin.checkRolePermission` | No |
445
+
446
+ Use Convex functions for custom admin operations. Use Better Auth client API for standard operations like user management, banning, and session management.