create-kuckit-app 0.3.5 → 0.4.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 (63) hide show
  1. package/dist/bin.js +1 -1
  2. package/dist/{create-project-CP-h4Ygi.js → create-project-CAsuZMK5.js} +7 -1
  3. package/dist/index.js +1 -1
  4. package/package.json +1 -1
  5. package/templates/base/.claude/CLAUDE.md +83 -0
  6. package/templates/base/.claude/skills/kuckit/SKILL.md +22 -2
  7. package/templates/base/.claude/skills/kuckit/references/MODULE-DEVELOPMENT.md +39 -28
  8. package/templates/base/.claude/skills/kuckit/references/PACKAGES.md +94 -74
  9. package/templates/base/AGENTS.md +130 -18
  10. package/templates/base/apps/server/AGENTS.md +44 -62
  11. package/templates/base/apps/server/package.json +5 -17
  12. package/templates/base/apps/server/src/config.ts +12 -0
  13. package/templates/base/apps/server/src/modules.ts +66 -0
  14. package/templates/base/apps/server/src/server.ts +4 -44
  15. package/templates/base/apps/web/AGENTS.md +63 -85
  16. package/templates/base/apps/web/package.json +7 -11
  17. package/templates/base/apps/web/src/components/KuckitModuleRoute.tsx +29 -7
  18. package/templates/base/apps/web/src/components/dashboard/dashboard-overview.tsx +2 -2
  19. package/templates/base/apps/web/src/components/dashboard/nav-user.tsx +2 -2
  20. package/templates/base/apps/web/src/main.tsx +12 -22
  21. package/templates/base/apps/web/src/modules.client.ts +43 -9
  22. package/templates/base/apps/web/src/routes/__root.tsx +1 -1
  23. package/templates/base/apps/web/src/routes/dashboard.tsx +1 -1
  24. package/templates/base/apps/web/src/routes/index.tsx +2 -2
  25. package/templates/base/apps/web/src/routes/login.tsx +2 -2
  26. package/templates/base/{packages/db/src/migrations → drizzle}/0000_init.sql +31 -36
  27. package/templates/base/{packages/db/src/migrations → drizzle}/meta/_journal.json +1 -1
  28. package/templates/base/drizzle.config.ts +34 -0
  29. package/templates/base/kuckit.config.ts +30 -0
  30. package/templates/base/package.json +14 -9
  31. package/templates/base/packages/items-module/AGENTS.md +83 -0
  32. package/templates/base/packages/items-module/package.json +7 -7
  33. package/templates/base/packages/items-module/src/api/items.router.ts +1 -1
  34. package/templates/base/apps/server/src/app.ts +0 -20
  35. package/templates/base/apps/server/src/auth.ts +0 -10
  36. package/templates/base/apps/server/src/config/modules.ts +0 -21
  37. package/templates/base/apps/server/src/container.ts +0 -81
  38. package/templates/base/apps/server/src/health.ts +0 -27
  39. package/templates/base/apps/server/src/middleware/container.ts +0 -41
  40. package/templates/base/apps/server/src/rpc-router-registry.ts +0 -26
  41. package/templates/base/apps/server/src/rpc.ts +0 -31
  42. package/templates/base/apps/web/src/providers/KuckitProvider.tsx +0 -123
  43. package/templates/base/apps/web/src/providers/ServicesProvider.tsx +0 -47
  44. package/templates/base/apps/web/src/services/auth-client.ts +0 -12
  45. package/templates/base/apps/web/src/services/index.ts +0 -3
  46. package/templates/base/apps/web/src/services/rpc.ts +0 -29
  47. package/templates/base/apps/web/src/services/types.ts +0 -14
  48. package/templates/base/packages/api/AGENTS.md +0 -66
  49. package/templates/base/packages/api/package.json +0 -35
  50. package/templates/base/packages/api/src/context.ts +0 -48
  51. package/templates/base/packages/api/src/index.ts +0 -22
  52. package/templates/base/packages/api/tsconfig.json +0 -8
  53. package/templates/base/packages/auth/AGENTS.md +0 -61
  54. package/templates/base/packages/auth/package.json +0 -27
  55. package/templates/base/packages/auth/src/index.ts +0 -22
  56. package/templates/base/packages/auth/tsconfig.json +0 -8
  57. package/templates/base/packages/db/AGENTS.md +0 -99
  58. package/templates/base/packages/db/drizzle.config.ts +0 -23
  59. package/templates/base/packages/db/package.json +0 -36
  60. package/templates/base/packages/db/src/connection.ts +0 -40
  61. package/templates/base/packages/db/src/index.ts +0 -4
  62. package/templates/base/packages/db/src/schema/auth.ts +0 -51
  63. package/templates/base/packages/db/tsconfig.json +0 -8
@@ -1,26 +0,0 @@
1
- import type { ApiRegistration } from '@kuckit/sdk'
2
-
3
- /**
4
- * Central mutable RPC router object used by RPCHandler.
5
- *
6
- * Module routers are wired into this object during the SDK loader's
7
- * "wire" phase, before RPCHandler is instantiated.
8
- */
9
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
10
- export const rootRpcRouter: Record<string, any> = {}
11
-
12
- /**
13
- * Wire module RPC routers into the central router object.
14
- * Called from the SDK loader's onApiRegistrations callback.
15
- */
16
- export const wireModuleRpcRouters = (registrations: ApiRegistration[]): void => {
17
- for (const reg of registrations) {
18
- if (reg.type !== 'rpc-router') continue
19
-
20
- if (rootRpcRouter[reg.name]) {
21
- throw new Error(`Duplicate RPC router name: "${reg.name}"`)
22
- }
23
-
24
- rootRpcRouter[reg.name] = reg.router
25
- }
26
- }
@@ -1,31 +0,0 @@
1
- import { RPCHandler } from '@orpc/server/node'
2
- import { onError } from '@orpc/server'
3
- import { createContext } from '@__APP_NAME_KEBAB__/api'
4
- import type { Express } from 'express'
5
- import { rootRpcRouter } from './rpc-router-registry'
6
-
7
- /**
8
- * Setup oRPC handler
9
- *
10
- * IMPORTANT: This must be called AFTER setupContainerMiddleware() has completed,
11
- * which ensures all module routers have been wired into rootRpcRouter.
12
- */
13
- export const setupRPC = (app: Express) => {
14
- const rpcHandler = new RPCHandler(rootRpcRouter, {
15
- interceptors: [
16
- onError((error) => {
17
- console.error('[RPC Error]', error)
18
- }),
19
- ],
20
- })
21
-
22
- app.use(async (req, res, next) => {
23
- const result = await rpcHandler.handle(req, res, {
24
- prefix: '/rpc',
25
- context: await createContext({ req }),
26
- })
27
-
28
- if (result.matched) return
29
- next()
30
- })
31
- }
@@ -1,123 +0,0 @@
1
- import { createContext, useContext, useEffect, useState, useMemo, type ReactNode } from 'react'
2
- import { RouterProvider, createRouter } from '@tanstack/react-router'
3
- import {
4
- loadKuckitClientModules,
5
- KuckitNavProvider,
6
- KuckitSlotProvider,
7
- KuckitRpcProvider,
8
- type LoadClientModulesResult,
9
- type RouteRegistry,
10
- type NavRegistry,
11
- type SlotRegistry,
12
- } from '@kuckit/sdk-react'
13
- import { routeTree } from '../routeTree.gen'
14
- import { useServices } from './ServicesProvider'
15
- import { getClientModuleSpecs } from '../modules.client'
16
-
17
- interface KuckitContextValue {
18
- routeRegistry: RouteRegistry
19
- navRegistry: NavRegistry
20
- slotRegistry: SlotRegistry
21
- isLoaded: boolean
22
- }
23
-
24
- const KuckitContext = createContext<KuckitContextValue | null>(null)
25
-
26
- /**
27
- * Hook to access Kuckit registries
28
- */
29
- export function useKuckit(): KuckitContextValue {
30
- const ctx = useContext(KuckitContext)
31
- if (!ctx) {
32
- throw new Error('useKuckit must be used within a KuckitProvider')
33
- }
34
- return ctx
35
- }
36
-
37
- interface KuckitProviderProps {
38
- children?: ReactNode
39
- }
40
-
41
- /**
42
- * KuckitProvider handles module loading and router creation
43
- */
44
- export function KuckitProvider({ children }: KuckitProviderProps) {
45
- const { orpc, queryClient, rpcClient } = useServices()
46
- const [loadResult, setLoadResult] = useState<LoadClientModulesResult | null>(null)
47
- const [error, setError] = useState<Error | null>(null)
48
-
49
- useEffect(() => {
50
- let cancelled = false
51
-
52
- const loadModules = async () => {
53
- try {
54
- const result = await loadKuckitClientModules({
55
- orpc,
56
- queryClient,
57
- env: import.meta.env.MODE,
58
- modules: getClientModuleSpecs(),
59
- })
60
-
61
- if (!cancelled) {
62
- setLoadResult(result)
63
- }
64
- } catch (err) {
65
- if (!cancelled) {
66
- setError(err instanceof Error ? err : new Error(String(err)))
67
- }
68
- }
69
- }
70
-
71
- loadModules()
72
-
73
- return () => {
74
- cancelled = true
75
- }
76
- }, [orpc, queryClient])
77
-
78
- const router = useMemo(() => {
79
- if (!loadResult) return null
80
- return createRouter({
81
- routeTree,
82
- defaultPreload: 'intent',
83
- context: { orpc, queryClient },
84
- })
85
- }, [loadResult, orpc, queryClient])
86
-
87
- if (error) {
88
- return (
89
- <div style={{ padding: '2rem', color: 'red' }}>
90
- <h2>Failed to load modules</h2>
91
- <p>{error.message}</p>
92
- </div>
93
- )
94
- }
95
-
96
- if (!loadResult || !router) {
97
- return (
98
- <div style={{ padding: '2rem' }}>
99
- <p>Loading...</p>
100
- </div>
101
- )
102
- }
103
-
104
- const contextValue: KuckitContextValue = {
105
- routeRegistry: loadResult.routeRegistry,
106
- navRegistry: loadResult.navRegistry,
107
- slotRegistry: loadResult.slotRegistry,
108
- isLoaded: true,
109
- }
110
-
111
- return (
112
- <KuckitContext.Provider value={contextValue}>
113
- <KuckitRpcProvider client={rpcClient}>
114
- <KuckitNavProvider registry={loadResult.navRegistry}>
115
- <KuckitSlotProvider registry={loadResult.slotRegistry}>
116
- <RouterProvider router={router} />
117
- {children}
118
- </KuckitSlotProvider>
119
- </KuckitNavProvider>
120
- </KuckitRpcProvider>
121
- </KuckitContext.Provider>
122
- )
123
- }
@@ -1,47 +0,0 @@
1
- import { createContext, useContext, useMemo, type ReactNode } from 'react'
2
- import { QueryClientProvider } from '@tanstack/react-query'
3
- import {
4
- createQueryClient,
5
- createRPCLink,
6
- createRPCClient,
7
- createORPCUtils,
8
- createAuthClientService,
9
- type Services,
10
- } from '../services'
11
-
12
- const ServicesContext = createContext<Services | null>(null)
13
-
14
- interface ServicesProviderProps {
15
- children: ReactNode
16
- }
17
-
18
- export function ServicesProvider({ children }: ServicesProviderProps) {
19
- const services = useMemo<Services>(() => {
20
- const queryClient = createQueryClient()
21
- const link = createRPCLink()
22
- const rpcClient = createRPCClient(link)
23
- const orpc = createORPCUtils(rpcClient)
24
- const authClient = createAuthClientService()
25
-
26
- return {
27
- queryClient,
28
- rpcClient,
29
- orpc,
30
- authClient,
31
- }
32
- }, [])
33
-
34
- return (
35
- <ServicesContext.Provider value={services}>
36
- <QueryClientProvider client={services.queryClient}>{children}</QueryClientProvider>
37
- </ServicesContext.Provider>
38
- )
39
- }
40
-
41
- export function useServices(): Services {
42
- const context = useContext(ServicesContext)
43
- if (!context) {
44
- throw new Error('useServices must be used within a ServicesProvider')
45
- }
46
- return context
47
- }
@@ -1,12 +0,0 @@
1
- import type { auth } from '@__APP_NAME_KEBAB__/auth'
2
- import { createAuthClient } from 'better-auth/react'
3
- import { inferAdditionalFields } from 'better-auth/client/plugins'
4
-
5
- export function createAuthClientService() {
6
- return createAuthClient({
7
- baseURL: import.meta.env.VITE_API_URL || 'http://localhost:3000',
8
- plugins: [inferAdditionalFields<typeof auth>()],
9
- })
10
- }
11
-
12
- export type AuthClient = ReturnType<typeof createAuthClientService>
@@ -1,3 +0,0 @@
1
- export * from './types'
2
- export * from './rpc'
3
- export * from './auth-client'
@@ -1,29 +0,0 @@
1
- import { createORPCClient } from '@orpc/client'
2
- import { RPCLink } from '@orpc/client/fetch'
3
- import { createTanstackQueryUtils } from '@orpc/tanstack-query'
4
- import { QueryClient } from '@tanstack/react-query'
5
- import type { RPCClient, ORPCUtils } from './types'
6
-
7
- export function createQueryClient(): QueryClient {
8
- return new QueryClient()
9
- }
10
-
11
- export function createRPCLink() {
12
- return new RPCLink({
13
- url: `${import.meta.env.VITE_API_URL || 'http://localhost:3000'}/rpc`,
14
- fetch(url, options) {
15
- return fetch(url, {
16
- ...options,
17
- credentials: 'include',
18
- })
19
- },
20
- })
21
- }
22
-
23
- export function createRPCClient(link: ReturnType<typeof createRPCLink>): RPCClient {
24
- return createORPCClient(link)
25
- }
26
-
27
- export function createORPCUtils(client: RPCClient): ORPCUtils {
28
- return createTanstackQueryUtils(client)
29
- }
@@ -1,14 +0,0 @@
1
- import type { QueryClient } from '@tanstack/react-query'
2
- import type { createTanstackQueryUtils } from '@orpc/tanstack-query'
3
- import type { AuthClient } from './auth-client'
4
-
5
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
6
- export type RPCClient = Record<string, any>
7
- export type ORPCUtils = ReturnType<typeof createTanstackQueryUtils<RPCClient>>
8
-
9
- export interface Services {
10
- queryClient: QueryClient
11
- rpcClient: RPCClient
12
- orpc: ORPCUtils
13
- authClient: AuthClient
14
- }
@@ -1,66 +0,0 @@
1
- # AGENTS.md - API Package
2
-
3
- > See root [AGENTS.md](../../AGENTS.md) for SDK Module System patterns
4
-
5
- ## Purpose
6
-
7
- Shared API context, types, and procedure definitions for oRPC.
8
-
9
- ## Key Files
10
-
11
- | File | Purpose |
12
- | ------------ | -------------------------------- |
13
- | `context.ts` | Request context type definitions |
14
- | `index.ts` | Public exports |
15
-
16
- ## Procedure Types
17
-
18
- ```typescript
19
- import { publicProcedure, protectedProcedure } from '@__APP_NAME_KEBAB__/api'
20
-
21
- // Public - no authentication required
22
- export const healthRouter = {
23
- ping: publicProcedure.handler(() => 'pong'),
24
- }
25
-
26
- // Protected - requires authenticated user
27
- export const itemsRouter = {
28
- list: protectedProcedure.handler(async ({ context }) => {
29
- const userId = context.session?.user?.id
30
- // ...
31
- }),
32
- }
33
- ```
34
-
35
- ## Context Structure
36
-
37
- The API context provides:
38
-
39
- - `di` - Scoped DI container for the request
40
- - `session` - Current user session (when using `protectedProcedure`)
41
-
42
- ## Typing DI Access in Routers
43
-
44
- Use a module-local interface to type `context.di.cradle`:
45
-
46
- ```typescript
47
- // In your module's router file
48
- interface ItemsCradle {
49
- itemRepository: ItemRepository
50
- createItem: (input: CreateItemInput) => Promise<Item>
51
- }
52
-
53
- export const itemsRouter = {
54
- create: protectedProcedure.input(createItemSchema).handler(async ({ input, context }) => {
55
- const { createItem } = context.di.cradle as ItemsCradle
56
- return createItem(input)
57
- }),
58
- }
59
- ```
60
-
61
- **Why module-local interfaces:**
62
-
63
- - Type assertion stays within the module
64
- - Module remains self-contained
65
- - Server app doesn't need to know about module internals
66
- - Scales to any number of modules without server changes
@@ -1,35 +0,0 @@
1
- {
2
- "name": "@__APP_NAME_KEBAB__/api",
3
- "version": "0.0.1",
4
- "private": true,
5
- "type": "module",
6
- "main": "src/index.ts",
7
- "types": "src/index.ts",
8
- "exports": {
9
- ".": {
10
- "types": "./src/index.ts",
11
- "default": "./src/index.ts"
12
- },
13
- "./*": {
14
- "types": "./src/*.ts",
15
- "default": "./src/*.ts"
16
- }
17
- },
18
- "devDependencies": {
19
- "@types/express": "^5.0.1"
20
- },
21
- "peerDependencies": {
22
- "typescript": "^5"
23
- },
24
- "dependencies": {
25
- "@orpc/server": "^1.10.0",
26
- "@orpc/client": "^1.10.0",
27
- "@orpc/zod": "^1.10.0",
28
- "better-auth": "^1.3.28",
29
- "dotenv": "^17.2.2",
30
- "zod": "^4.1.11",
31
- "awilix": "^12.0.5",
32
- "@__APP_NAME_KEBAB__/auth": "workspace:*",
33
- "@__APP_NAME_KEBAB__/db": "workspace:*"
34
- }
35
- }
@@ -1,48 +0,0 @@
1
- import type { Request } from 'express'
2
- import { fromNodeHeaders } from 'better-auth/node'
3
- import { auth } from '@__APP_NAME_KEBAB__/auth'
4
- import { asValue, type AwilixContainer } from 'awilix'
5
-
6
- declare global {
7
- // eslint-disable-next-line @typescript-eslint/no-namespace
8
- namespace Express {
9
- interface Request {
10
- scope?: AwilixContainer
11
- }
12
- }
13
- }
14
-
15
- export interface CreateContextOptions {
16
- req: Request
17
- }
18
-
19
- /**
20
- * Create per-request context with DI container
21
- */
22
- export async function createContext(opts: CreateContextOptions) {
23
- let session
24
- try {
25
- session = await auth.api.getSession({
26
- headers: fromNodeHeaders(opts.req.headers),
27
- })
28
- } catch (error) {
29
- console.error('[Auth] Failed to get session:', error)
30
- session = null
31
- }
32
-
33
- const requestId = crypto.randomUUID()
34
-
35
- if (opts.req.scope) {
36
- opts.req.scope.register({
37
- session: asValue(session),
38
- requestId: asValue(requestId),
39
- })
40
- }
41
-
42
- return {
43
- session,
44
- di: opts.req.scope!,
45
- }
46
- }
47
-
48
- export type Context = Awaited<ReturnType<typeof createContext>>
@@ -1,22 +0,0 @@
1
- import { ORPCError, os } from '@orpc/server'
2
- import type { Context } from './context'
3
-
4
- export const o = os.$context<Context>()
5
-
6
- export const publicProcedure = o
7
-
8
- const requireAuth = o.middleware(async ({ context, next }) => {
9
- if (!context.session?.user) {
10
- throw new ORPCError('UNAUTHORIZED')
11
- }
12
- return next({
13
- context: {
14
- session: context.session,
15
- },
16
- })
17
- })
18
-
19
- export const protectedProcedure = publicProcedure.use(requireAuth)
20
-
21
- export { createContext } from './context'
22
- export type { Context } from './context'
@@ -1,8 +0,0 @@
1
- {
2
- "extends": "../../tsconfig.base.json",
3
- "compilerOptions": {
4
- "outDir": "dist",
5
- "rootDir": "src"
6
- },
7
- "include": ["src/**/*"]
8
- }
@@ -1,61 +0,0 @@
1
- # AGENTS.md - Auth Package
2
-
3
- > See root [AGENTS.md](../../AGENTS.md) for SDK Module System patterns
4
-
5
- ## Purpose
6
-
7
- Better-Auth configuration shared between server and web apps.
8
-
9
- ## Key Files
10
-
11
- | File | Purpose |
12
- | ---------- | -------------------------------------- |
13
- | `index.ts` | Auth configuration and client creation |
14
-
15
- ## Server Usage
16
-
17
- ```typescript
18
- import { auth } from '@__APP_NAME_KEBAB__/auth'
19
-
20
- // In Express
21
- app.all('/api/auth/*', auth.handler)
22
- ```
23
-
24
- ## Client Usage
25
-
26
- ```typescript
27
- import { authClient } from '@__APP_NAME_KEBAB__/auth'
28
-
29
- // Sign in
30
- await authClient.signIn.email({ email, password })
31
-
32
- // Sign out
33
- await authClient.signOut()
34
-
35
- // Get session
36
- const session = await authClient.getSession()
37
- ```
38
-
39
- ## Session in oRPC Context
40
-
41
- The current user session is available in `protectedProcedure` handlers:
42
-
43
- ```typescript
44
- import { protectedProcedure } from '@__APP_NAME_KEBAB__/api'
45
-
46
- export const myRouter = {
47
- getProfile: protectedProcedure.handler(async ({ context }) => {
48
- const userId = context.session?.user?.id
49
- const userEmail = context.session?.user?.email
50
- // ... use session data
51
- }),
52
- }
53
- ```
54
-
55
- ## Configuration
56
-
57
- Auth requires these environment variables:
58
-
59
- - `BETTER_AUTH_SECRET` - Session encryption key
60
- - `BETTER_AUTH_URL` - Callback URL for OAuth
61
- - `DATABASE_URL` - For session storage
@@ -1,27 +0,0 @@
1
- {
2
- "name": "@__APP_NAME_KEBAB__/auth",
3
- "version": "0.0.1",
4
- "private": true,
5
- "type": "module",
6
- "main": "src/index.ts",
7
- "types": "src/index.ts",
8
- "exports": {
9
- ".": {
10
- "types": "./src/index.ts",
11
- "default": "./src/index.ts"
12
- },
13
- "./*": {
14
- "types": "./src/*.ts",
15
- "default": "./src/*.ts"
16
- }
17
- },
18
- "peerDependencies": {
19
- "typescript": "^5"
20
- },
21
- "dependencies": {
22
- "better-auth": "^1.3.28",
23
- "dotenv": "^17.2.2",
24
- "zod": "^4.1.11",
25
- "@__APP_NAME_KEBAB__/db": "workspace:*"
26
- }
27
- }
@@ -1,22 +0,0 @@
1
- import { betterAuth, type BetterAuthOptions } from 'better-auth'
2
- import { drizzleAdapter } from 'better-auth/adapters/drizzle'
3
- import { db } from '@__APP_NAME_KEBAB__/db'
4
- import * as schema from '@__APP_NAME_KEBAB__/db/schema/auth'
5
-
6
- export const auth = betterAuth<BetterAuthOptions>({
7
- database: drizzleAdapter(db, {
8
- provider: 'pg',
9
- schema: schema,
10
- }),
11
- trustedOrigins: [process.env.CORS_ORIGIN || ''],
12
- emailAndPassword: {
13
- enabled: true,
14
- },
15
- advanced: {
16
- defaultCookieAttributes: {
17
- sameSite: process.env.NODE_ENV === 'production' ? 'none' : 'lax',
18
- secure: process.env.NODE_ENV === 'production',
19
- httpOnly: true,
20
- },
21
- },
22
- })
@@ -1,8 +0,0 @@
1
- {
2
- "extends": "../../tsconfig.base.json",
3
- "compilerOptions": {
4
- "outDir": "dist",
5
- "rootDir": "src"
6
- },
7
- "include": ["src/**/*"]
8
- }