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.
- package/dist/bin.js +1 -1
- package/dist/{create-project-CP-h4Ygi.js → create-project-CAsuZMK5.js} +7 -1
- package/dist/index.js +1 -1
- package/package.json +1 -1
- package/templates/base/.claude/CLAUDE.md +83 -0
- package/templates/base/.claude/skills/kuckit/SKILL.md +22 -2
- package/templates/base/.claude/skills/kuckit/references/MODULE-DEVELOPMENT.md +39 -28
- package/templates/base/.claude/skills/kuckit/references/PACKAGES.md +94 -74
- package/templates/base/AGENTS.md +130 -18
- package/templates/base/apps/server/AGENTS.md +44 -62
- package/templates/base/apps/server/package.json +5 -17
- package/templates/base/apps/server/src/config.ts +12 -0
- package/templates/base/apps/server/src/modules.ts +66 -0
- package/templates/base/apps/server/src/server.ts +4 -44
- package/templates/base/apps/web/AGENTS.md +63 -85
- package/templates/base/apps/web/package.json +7 -11
- package/templates/base/apps/web/src/components/KuckitModuleRoute.tsx +29 -7
- package/templates/base/apps/web/src/components/dashboard/dashboard-overview.tsx +2 -2
- package/templates/base/apps/web/src/components/dashboard/nav-user.tsx +2 -2
- package/templates/base/apps/web/src/main.tsx +12 -22
- package/templates/base/apps/web/src/modules.client.ts +43 -9
- package/templates/base/apps/web/src/routes/__root.tsx +1 -1
- package/templates/base/apps/web/src/routes/dashboard.tsx +1 -1
- package/templates/base/apps/web/src/routes/index.tsx +2 -2
- package/templates/base/apps/web/src/routes/login.tsx +2 -2
- package/templates/base/{packages/db/src/migrations → drizzle}/0000_init.sql +31 -36
- package/templates/base/{packages/db/src/migrations → drizzle}/meta/_journal.json +1 -1
- package/templates/base/drizzle.config.ts +34 -0
- package/templates/base/kuckit.config.ts +30 -0
- package/templates/base/package.json +14 -9
- package/templates/base/packages/items-module/AGENTS.md +83 -0
- package/templates/base/packages/items-module/package.json +7 -7
- package/templates/base/packages/items-module/src/api/items.router.ts +1 -1
- package/templates/base/apps/server/src/app.ts +0 -20
- package/templates/base/apps/server/src/auth.ts +0 -10
- package/templates/base/apps/server/src/config/modules.ts +0 -21
- package/templates/base/apps/server/src/container.ts +0 -81
- package/templates/base/apps/server/src/health.ts +0 -27
- package/templates/base/apps/server/src/middleware/container.ts +0 -41
- package/templates/base/apps/server/src/rpc-router-registry.ts +0 -26
- package/templates/base/apps/server/src/rpc.ts +0 -31
- package/templates/base/apps/web/src/providers/KuckitProvider.tsx +0 -123
- package/templates/base/apps/web/src/providers/ServicesProvider.tsx +0 -47
- package/templates/base/apps/web/src/services/auth-client.ts +0 -12
- package/templates/base/apps/web/src/services/index.ts +0 -3
- package/templates/base/apps/web/src/services/rpc.ts +0 -29
- package/templates/base/apps/web/src/services/types.ts +0 -14
- package/templates/base/packages/api/AGENTS.md +0 -66
- package/templates/base/packages/api/package.json +0 -35
- package/templates/base/packages/api/src/context.ts +0 -48
- package/templates/base/packages/api/src/index.ts +0 -22
- package/templates/base/packages/api/tsconfig.json +0 -8
- package/templates/base/packages/auth/AGENTS.md +0 -61
- package/templates/base/packages/auth/package.json +0 -27
- package/templates/base/packages/auth/src/index.ts +0 -22
- package/templates/base/packages/auth/tsconfig.json +0 -8
- package/templates/base/packages/db/AGENTS.md +0 -99
- package/templates/base/packages/db/drizzle.config.ts +0 -23
- package/templates/base/packages/db/package.json +0 -36
- package/templates/base/packages/db/src/connection.ts +0 -40
- package/templates/base/packages/db/src/index.ts +0 -4
- package/templates/base/packages/db/src/schema/auth.ts +0 -51
- package/templates/base/packages/db/tsconfig.json +0 -8
|
@@ -8,86 +8,68 @@ Express backend hosting oRPC API, Better-Auth authentication, and the Kuckit mod
|
|
|
8
8
|
|
|
9
9
|
## Key Files
|
|
10
10
|
|
|
11
|
-
| File
|
|
12
|
-
|
|
|
13
|
-
| `server.ts`
|
|
14
|
-
| `
|
|
15
|
-
| `
|
|
16
|
-
| `rpc.ts` | oRPC handler setup |
|
|
17
|
-
| `rpc-router-registry.ts` | Mutable router for modules |
|
|
18
|
-
| `auth.ts` | Better-Auth configuration |
|
|
11
|
+
| File | Purpose |
|
|
12
|
+
| ------------ | ------------------------------------------------- |
|
|
13
|
+
| `server.ts` | Entry point (9 lines) |
|
|
14
|
+
| `config.ts` | Server configuration |
|
|
15
|
+
| `modules.ts` | Module registration (reads from kuckit.config.ts) |
|
|
19
16
|
|
|
20
|
-
##
|
|
17
|
+
## How It Works
|
|
21
18
|
|
|
22
|
-
The server
|
|
23
|
-
|
|
24
|
-
```
|
|
25
|
-
1. createKuckitContainer() - Core services (db, logger, cache, etc.)
|
|
26
|
-
2. loadKuckitModules() with onApiRegistrations callback:
|
|
27
|
-
├─► register() hooks run (DI bindings)
|
|
28
|
-
├─► registerApi() hooks run (routers collected)
|
|
29
|
-
├─► onApiRegistrations() - Wire routers to rootRpcRouter
|
|
30
|
-
└─► onBootstrap() hooks run (startup logic)
|
|
31
|
-
3. setupRPC() - Create RPCHandler AFTER modules loaded
|
|
32
|
-
4. Express listens
|
|
33
|
-
```
|
|
34
|
-
|
|
35
|
-
## Router Registry Pattern
|
|
36
|
-
|
|
37
|
-
oRPC's `RPCHandler` captures the router at construction time. Modules wire their routers into a **mutable object** before the handler is created:
|
|
38
|
-
|
|
39
|
-
```typescript
|
|
40
|
-
// rpc-router-registry.ts
|
|
41
|
-
export const rootRpcRouter = { ...appRouter }
|
|
42
|
-
|
|
43
|
-
export const wireModuleRpcRouters = (registrations: ApiRegistration[]) => {
|
|
44
|
-
for (const reg of registrations) {
|
|
45
|
-
if (reg.type === 'rpc-router') {
|
|
46
|
-
rootRpcRouter[reg.name] = reg.router
|
|
47
|
-
}
|
|
48
|
-
}
|
|
49
|
-
}
|
|
50
|
-
```
|
|
19
|
+
The server uses `@kuckit/app-server` which handles all the complexity internally:
|
|
51
20
|
|
|
52
21
|
```typescript
|
|
53
22
|
// server.ts
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
wireModuleRpcRouters(registrations) // Wire BEFORE setupRPC()
|
|
59
|
-
},
|
|
60
|
-
})
|
|
61
|
-
|
|
62
|
-
setupRPC(app) // Now create handler with fully-wired router
|
|
63
|
-
```
|
|
64
|
-
|
|
65
|
-
## Per-Request Scoping
|
|
23
|
+
import 'dotenv/config'
|
|
24
|
+
import { runKuckitServer } from '@kuckit/app-server'
|
|
25
|
+
import { loadConfig } from './config'
|
|
26
|
+
import { getModuleSpecs } from './modules'
|
|
66
27
|
|
|
67
|
-
|
|
28
|
+
runKuckitServer({ loadConfig, getModuleSpecs }).catch(console.error)
|
|
29
|
+
```
|
|
68
30
|
|
|
69
|
-
-
|
|
70
|
-
- `requestLogger` - Logger with request context
|
|
71
|
-
- `session` - Current user session (if authenticated)
|
|
31
|
+
The `@kuckit/app-server` package handles:
|
|
72
32
|
|
|
73
|
-
|
|
33
|
+
- DI container creation and setup
|
|
34
|
+
- Module loading with proper lifecycle hooks
|
|
35
|
+
- RPC router wiring (oRPC)
|
|
36
|
+
- REST router mounting
|
|
37
|
+
- Authentication (Better-Auth)
|
|
38
|
+
- Health endpoints
|
|
39
|
+
- Graceful shutdown
|
|
74
40
|
|
|
75
41
|
## Adding New Modules
|
|
76
42
|
|
|
77
|
-
|
|
78
|
-
|
|
43
|
+
**`kuckit.config.ts` is the single source of truth for modules.**
|
|
44
|
+
|
|
45
|
+
1. Add to `kuckit.config.ts` (at project root):
|
|
79
46
|
|
|
80
47
|
```typescript
|
|
81
|
-
|
|
48
|
+
export default defineConfig({
|
|
49
|
+
modules: [
|
|
50
|
+
{ package: '@__APP_NAME_KEBAB__/items-module' },
|
|
51
|
+
{ package: '@acme/billing-module' }, // Add new module here
|
|
52
|
+
],
|
|
53
|
+
})
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
2. Add import and mapping to `modules.ts`:
|
|
82
57
|
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
58
|
+
```typescript
|
|
59
|
+
import { kuckitModule as billingModule } from '@acme/billing-module'
|
|
60
|
+
|
|
61
|
+
const KNOWN_MODULES = {
|
|
62
|
+
// ...existing
|
|
63
|
+
'@acme/billing-module': {
|
|
64
|
+
module: billingModule,
|
|
65
|
+
},
|
|
66
|
+
}
|
|
87
67
|
```
|
|
88
68
|
|
|
89
69
|
3. Restart the server
|
|
90
70
|
|
|
71
|
+
The CLI command `bunx kuckit add @acme/billing-module` automates both steps.
|
|
72
|
+
|
|
91
73
|
## Environment Variables
|
|
92
74
|
|
|
93
75
|
See `.env.example` in this directory.
|
|
@@ -5,27 +5,15 @@
|
|
|
5
5
|
"scripts": {
|
|
6
6
|
"build": "tsdown",
|
|
7
7
|
"check-types": "tsc -b",
|
|
8
|
-
"dev": "
|
|
8
|
+
"dev": "kuckit dev"
|
|
9
9
|
},
|
|
10
10
|
"dependencies": {
|
|
11
|
-
"
|
|
12
|
-
"
|
|
13
|
-
"
|
|
14
|
-
"
|
|
15
|
-
"pg": "^8.14.1",
|
|
16
|
-
"@orpc/server": "^1.10.0",
|
|
17
|
-
"@orpc/zod": "^1.10.0",
|
|
18
|
-
"better-auth": "^1.3.28",
|
|
19
|
-
"zod": "^4.1.11",
|
|
20
|
-
"@kuckit/sdk": "^1.0.0",
|
|
21
|
-
"@__APP_NAME_KEBAB__/api": "workspace:*",
|
|
22
|
-
"@__APP_NAME_KEBAB__/auth": "workspace:*",
|
|
23
|
-
"@__APP_NAME_KEBAB__/db": "workspace:*"
|
|
11
|
+
"dotenv": "^17.0.0",
|
|
12
|
+
"@kuckit/app-server": "^2.0.0",
|
|
13
|
+
"@kuckit/sdk": "^2.0.0",
|
|
14
|
+
"@kuckit/db": "^2.0.0"
|
|
24
15
|
},
|
|
25
16
|
"devDependencies": {
|
|
26
|
-
"@types/express": "^5.0.1",
|
|
27
|
-
"@types/cors": "^2.8.17",
|
|
28
|
-
"@types/pg": "^8.11.11",
|
|
29
17
|
"typescript": "^5.8.2",
|
|
30
18
|
"tsdown": "^0.15.5"
|
|
31
19
|
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { ensureDatabaseUrl } from '@kuckit/db/connection'
|
|
2
|
+
import type { KuckitServerConfig } from '@kuckit/app-server'
|
|
3
|
+
|
|
4
|
+
export const loadConfig = (): KuckitServerConfig => ({
|
|
5
|
+
databaseUrl: ensureDatabaseUrl(),
|
|
6
|
+
enableFileLogging: process.env.ENABLE_FILE_LOGGING === 'true',
|
|
7
|
+
logDir: process.env.LOG_DIR || './logs',
|
|
8
|
+
logLevel: (process.env.LOG_LEVEL || 'INFO') as 'DEBUG' | 'INFO' | 'WARN' | 'ERROR',
|
|
9
|
+
env: process.env.NODE_ENV || 'development',
|
|
10
|
+
port: parseInt(process.env.PORT || '3000', 10),
|
|
11
|
+
corsOrigin: process.env.CORS_ORIGIN || 'http://localhost:3001',
|
|
12
|
+
})
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import type { ModuleSpec, KuckitConfig } from '@kuckit/sdk'
|
|
2
|
+
import { tryLoadKuckitConfig } from '@kuckit/sdk'
|
|
3
|
+
import { kuckitModule as itemsModule } from '@__APP_NAME_KEBAB__/items-module'
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Known modules mapping: package name → direct module import
|
|
7
|
+
*
|
|
8
|
+
* This enables config-driven module loading while keeping direct imports
|
|
9
|
+
* for workspace packages (required for monorepo compatibility).
|
|
10
|
+
*
|
|
11
|
+
* When adding a new module:
|
|
12
|
+
* 1. Add to kuckit.config.ts (source of truth)
|
|
13
|
+
* 2. Add import and mapping here
|
|
14
|
+
*/
|
|
15
|
+
const KNOWN_MODULES: Record<string, { module: unknown; defaultConfig?: unknown }> = {
|
|
16
|
+
// KUCKIT_KNOWN_MODULES_START
|
|
17
|
+
'@__APP_NAME_KEBAB__/items-module': {
|
|
18
|
+
module: itemsModule,
|
|
19
|
+
},
|
|
20
|
+
// KUCKIT_KNOWN_MODULES_END
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Convert unified config to ModuleSpec array.
|
|
25
|
+
* For known modules, uses direct imports. For npm packages, uses package-based loading.
|
|
26
|
+
*/
|
|
27
|
+
function configToModuleSpecs(config: KuckitConfig): ModuleSpec[] {
|
|
28
|
+
return config.modules
|
|
29
|
+
.filter((m) => m.enabled !== false)
|
|
30
|
+
.map((m) => {
|
|
31
|
+
const known = KNOWN_MODULES[m.package]
|
|
32
|
+
if (known) {
|
|
33
|
+
return {
|
|
34
|
+
module: known.module,
|
|
35
|
+
config: m.config ?? known.defaultConfig,
|
|
36
|
+
} as ModuleSpec
|
|
37
|
+
}
|
|
38
|
+
// For npm packages, use package-based loading
|
|
39
|
+
return {
|
|
40
|
+
package: m.package,
|
|
41
|
+
config: m.config,
|
|
42
|
+
} as ModuleSpec
|
|
43
|
+
})
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Fallback module specs (used when kuckit.config.ts is not found)
|
|
48
|
+
*/
|
|
49
|
+
const getFallbackModuleSpecs = (): ModuleSpec[] => [{ module: itemsModule }]
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Get module specs from kuckit.config.ts (single source of truth).
|
|
53
|
+
* Falls back to direct module specs if config is not found.
|
|
54
|
+
*/
|
|
55
|
+
export const getModuleSpecs = async (): Promise<ModuleSpec[]> => {
|
|
56
|
+
const config = await tryLoadKuckitConfig()
|
|
57
|
+
|
|
58
|
+
if (config) {
|
|
59
|
+
console.log(`[kuckit] Loading modules from: ${config._configPath}`)
|
|
60
|
+
return configToModuleSpecs(config)
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// Fallback for legacy projects without kuckit.config.ts
|
|
64
|
+
console.log('[kuckit] No config found, using fallback module specs')
|
|
65
|
+
return getFallbackModuleSpecs()
|
|
66
|
+
}
|
|
@@ -1,49 +1,9 @@
|
|
|
1
1
|
import 'dotenv/config'
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
import { setupRPC } from './rpc'
|
|
6
|
-
import { setupHealth } from './health'
|
|
7
|
-
import { disposeContainer } from './container'
|
|
2
|
+
import { runKuckitServer } from '@kuckit/app-server'
|
|
3
|
+
import { loadConfig } from './config'
|
|
4
|
+
import { getModuleSpecs } from './modules'
|
|
8
5
|
|
|
9
|
-
|
|
10
|
-
* Bootstrap and start server
|
|
11
|
-
*/
|
|
12
|
-
const bootstrap = async () => {
|
|
13
|
-
const app = createApp()
|
|
14
|
-
|
|
15
|
-
await setupContainerMiddleware(app)
|
|
16
|
-
const rootContainer = getRootContainer()
|
|
17
|
-
|
|
18
|
-
setupAuth(app)
|
|
19
|
-
setupRPC(app)
|
|
20
|
-
setupHealth(app, rootContainer)
|
|
21
|
-
|
|
22
|
-
const port = process.env.PORT || 3000
|
|
23
|
-
const server = app.listen(port, () => {
|
|
24
|
-
console.log(`Server is running on port ${port}`)
|
|
25
|
-
})
|
|
26
|
-
|
|
27
|
-
const shutdown = async () => {
|
|
28
|
-
console.log('Shutting down gracefully...')
|
|
29
|
-
|
|
30
|
-
server.close(async () => {
|
|
31
|
-
await disposeContainer(rootContainer)
|
|
32
|
-
console.log('Server closed')
|
|
33
|
-
process.exit(0)
|
|
34
|
-
})
|
|
35
|
-
|
|
36
|
-
setTimeout(() => {
|
|
37
|
-
console.error('Forced shutdown')
|
|
38
|
-
process.exit(1)
|
|
39
|
-
}, 10000)
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
process.on('SIGTERM', shutdown)
|
|
43
|
-
process.on('SIGINT', shutdown)
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
bootstrap().catch((error) => {
|
|
6
|
+
runKuckitServer({ loadConfig, getModuleSpecs }).catch((error) => {
|
|
47
7
|
console.error('Failed to start server:', error)
|
|
48
8
|
process.exit(1)
|
|
49
9
|
})
|
|
@@ -10,118 +10,96 @@ React frontend using TanStack Router, TanStack Query, and Kuckit client modules.
|
|
|
10
10
|
|
|
11
11
|
| File | Purpose |
|
|
12
12
|
| ---------------------------------- | --------------------------------- |
|
|
13
|
-
| `main.tsx` | App entry point
|
|
13
|
+
| `main.tsx` | App entry point (17 lines) |
|
|
14
14
|
| `modules.client.ts` | Client module registration |
|
|
15
|
-
| `providers/KuckitProvider.tsx` | Kuckit context setup |
|
|
16
|
-
| `providers/ServicesProvider.tsx` | Services context (RPC, auth) |
|
|
17
15
|
| `routes/` | TanStack Router file-based routes |
|
|
18
16
|
| `routes/$.tsx` | Catch-all for module routes |
|
|
19
17
|
| `components/KuckitModuleRoute.tsx` | Renders module-registered routes |
|
|
20
18
|
|
|
21
|
-
##
|
|
22
|
-
|
|
23
|
-
Create files in `src/routes/`:
|
|
24
|
-
|
|
25
|
-
- `src/routes/about.tsx` → `/about`
|
|
26
|
-
- `src/routes/users/$id.tsx` → `/users/:id`
|
|
27
|
-
- `src/routes/settings/index.tsx` → `/settings`
|
|
28
|
-
|
|
29
|
-
**File-based routes take precedence** over module-registered routes.
|
|
19
|
+
## How It Works
|
|
30
20
|
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
Modules register routes via `defineKuckitClientModule`. These are rendered via a catch-all pattern:
|
|
34
|
-
|
|
35
|
-
1. Module registers route in `routes` array
|
|
36
|
-
2. `RouteRegistry` collects routes during `loadKuckitClientModules()`
|
|
37
|
-
3. Catch-all route (`$.tsx`) matches any unhandled path
|
|
38
|
-
4. `KuckitModuleRoute` looks up path in registry and renders the component
|
|
21
|
+
The web app uses `@kuckit/app-web` which provides a provider factory:
|
|
39
22
|
|
|
40
23
|
```tsx
|
|
41
|
-
//
|
|
42
|
-
import
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
}
|
|
24
|
+
// main.tsx
|
|
25
|
+
import ReactDOM from 'react-dom/client'
|
|
26
|
+
import { createKuckitWebProvider } from '@kuckit/app-web'
|
|
27
|
+
import { routeTree } from './routeTree.gen'
|
|
28
|
+
import { Route as rootRoute } from './routes/__root'
|
|
29
|
+
import { getClientModuleSpecs } from './modules.client'
|
|
30
|
+
import './index.css'
|
|
31
|
+
|
|
32
|
+
const KuckitApp = createKuckitWebProvider({
|
|
33
|
+
serverUrl: import.meta.env.VITE_API_URL || 'http://localhost:3000',
|
|
34
|
+
modules: getClientModuleSpecs(),
|
|
35
|
+
env: import.meta.env.MODE,
|
|
36
|
+
routeTree,
|
|
37
|
+
rootRoute,
|
|
38
|
+
})
|
|
39
|
+
|
|
40
|
+
const rootEl = document.getElementById('app')!
|
|
41
|
+
ReactDOM.createRoot(rootEl).render(<KuckitApp />)
|
|
47
42
|
```
|
|
48
43
|
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
The app wraps content in Kuckit providers:
|
|
44
|
+
The `@kuckit/app-web` package handles:
|
|
52
45
|
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
{/* Nav, slots, routes registries */}
|
|
60
|
-
<KuckitRpcProvider>
|
|
61
|
-
{' '}
|
|
62
|
-
{/* RPC context for modules */}
|
|
63
|
-
{children}
|
|
64
|
-
</KuckitRpcProvider>
|
|
65
|
-
</KuckitProvider>
|
|
66
|
-
</ServicesProvider>
|
|
67
|
-
```
|
|
46
|
+
- RPC client and oRPC utils creation
|
|
47
|
+
- React Query client setup
|
|
48
|
+
- Better-Auth client integration
|
|
49
|
+
- Module loading and route registration
|
|
50
|
+
- Navigation and slot registries
|
|
51
|
+
- Provider composition
|
|
68
52
|
|
|
69
|
-
|
|
53
|
+
## Available Hooks
|
|
70
54
|
|
|
71
|
-
|
|
72
|
-
- `useNavItems()` - Flat navigation items list
|
|
73
|
-
- `useNavTree()` - Hierarchical navigation tree
|
|
74
|
-
- `useSlot(name)` - Get components for a slot
|
|
75
|
-
- `useHasSlot(name)` - Check if slot has content
|
|
55
|
+
Import from `@kuckit/app-web`:
|
|
76
56
|
|
|
77
|
-
|
|
57
|
+
- `useAuth()` - Auth client for sign in/out, session
|
|
58
|
+
- `useRpc()` - Typed RPC client for API calls
|
|
59
|
+
- `useOrpc()` - oRPC TanStack Query utils
|
|
60
|
+
- `useQueryClient()` - React Query client
|
|
61
|
+
- `useKuckitWeb()` - Full context with registries
|
|
78
62
|
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
```tsx
|
|
82
|
-
import { useRpc } from '@kuckit/sdk-react'
|
|
83
|
-
import { useQuery } from '@tanstack/react-query'
|
|
84
|
-
|
|
85
|
-
interface MyRpc {
|
|
86
|
-
items: { list: (input: {}) => Promise<Item[]> }
|
|
87
|
-
}
|
|
63
|
+
## Adding Routes
|
|
88
64
|
|
|
89
|
-
|
|
90
|
-
const rpc = useRpc<MyRpc>()
|
|
65
|
+
Create files in `src/routes/`:
|
|
91
66
|
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
})
|
|
96
|
-
}
|
|
97
|
-
```
|
|
67
|
+
- `src/routes/about.tsx` → `/about`
|
|
68
|
+
- `src/routes/users/$id.tsx` → `/users/:id`
|
|
69
|
+
- `src/routes/settings/index.tsx` → `/settings`
|
|
98
70
|
|
|
99
|
-
**
|
|
71
|
+
**File-based routes take precedence** over module-registered routes.
|
|
100
72
|
|
|
101
|
-
##
|
|
73
|
+
## Adding Client Modules
|
|
102
74
|
|
|
103
|
-
|
|
104
|
-
import { ItemsPage } from '@__APP_NAME_KEBAB__/items-module/ui'
|
|
75
|
+
**`kuckit.config.ts` is the single source of truth for modules.**
|
|
105
76
|
|
|
106
|
-
|
|
107
|
-
export function Route() {
|
|
108
|
-
return <ItemsPage />
|
|
109
|
-
}
|
|
110
|
-
```
|
|
77
|
+
1. Add to `kuckit.config.ts` (at project root):
|
|
111
78
|
|
|
112
|
-
|
|
79
|
+
```typescript
|
|
80
|
+
export default defineConfig({
|
|
81
|
+
modules: [
|
|
82
|
+
{ package: '@__APP_NAME_KEBAB__/items-module' },
|
|
83
|
+
{ package: '@acme/billing-module' }, // Add new module here
|
|
84
|
+
],
|
|
85
|
+
})
|
|
86
|
+
```
|
|
113
87
|
|
|
114
|
-
|
|
88
|
+
2. Add import and mapping to `modules.client.ts`:
|
|
115
89
|
|
|
116
90
|
```typescript
|
|
117
|
-
import { kuckitClientModule as
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
91
|
+
import { kuckitClientModule as billingClientModule } from '@acme/billing-module/client'
|
|
92
|
+
|
|
93
|
+
const KNOWN_CLIENT_MODULES = {
|
|
94
|
+
// ...existing
|
|
95
|
+
'@acme/billing-module': {
|
|
96
|
+
module: billingClientModule,
|
|
97
|
+
},
|
|
98
|
+
}
|
|
123
99
|
```
|
|
124
100
|
|
|
101
|
+
The CLI command `bunx kuckit add @acme/billing-module` automates both steps.
|
|
102
|
+
|
|
125
103
|
## Environment Variables
|
|
126
104
|
|
|
127
105
|
See `.env.example` in this directory.
|
|
@@ -9,9 +9,8 @@
|
|
|
9
9
|
"check-types": "tsc --noEmit"
|
|
10
10
|
},
|
|
11
11
|
"dependencies": {
|
|
12
|
-
"@kuckit/
|
|
13
|
-
"@
|
|
14
|
-
"@orpc/tanstack-query": "^1.10.0",
|
|
12
|
+
"@kuckit/app-web": "^2.0.0",
|
|
13
|
+
"@kuckit/sdk-react": "^2.0.0",
|
|
15
14
|
"@radix-ui/react-avatar": "^1.1.10",
|
|
16
15
|
"@radix-ui/react-collapsible": "^1.1.11",
|
|
17
16
|
"@radix-ui/react-dialog": "^1.1.14",
|
|
@@ -19,17 +18,14 @@
|
|
|
19
18
|
"@radix-ui/react-separator": "^1.1.7",
|
|
20
19
|
"@radix-ui/react-slot": "^1.2.3",
|
|
21
20
|
"@radix-ui/react-tooltip": "^1.2.7",
|
|
22
|
-
"@tanstack/react-query": "^5.
|
|
23
|
-
"@tanstack/react-router": "^1.114.
|
|
24
|
-
"better-auth": "^1.3.28",
|
|
21
|
+
"@tanstack/react-query": "^5.0.0",
|
|
22
|
+
"@tanstack/react-router": "^1.114.0",
|
|
25
23
|
"class-variance-authority": "^0.7.1",
|
|
26
24
|
"clsx": "^2.1.1",
|
|
27
25
|
"lucide-react": "^0.511.0",
|
|
28
|
-
"react": "^19.
|
|
29
|
-
"react-dom": "^19.
|
|
30
|
-
"tailwind-merge": "^3.
|
|
31
|
-
"zod": "^4.1.11",
|
|
32
|
-
"@__APP_NAME_KEBAB__/auth": "workspace:*"
|
|
26
|
+
"react": "^19.0.0",
|
|
27
|
+
"react-dom": "^19.0.0",
|
|
28
|
+
"tailwind-merge": "^3.0.0"
|
|
33
29
|
},
|
|
34
30
|
"devDependencies": {
|
|
35
31
|
"@tailwindcss/vite": "^4.0.15",
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import { useRouterState, Link, useNavigate } from '@tanstack/react-router'
|
|
2
|
-
import {
|
|
3
|
-
import { useServices } from '@/providers/ServicesProvider'
|
|
2
|
+
import { useKuckitWeb, useAuth } from '@kuckit/app-web'
|
|
4
3
|
import { useEffect, useState } from 'react'
|
|
5
4
|
|
|
6
5
|
/**
|
|
@@ -24,8 +23,8 @@ function isRootLevelRoute(path: string): boolean {
|
|
|
24
23
|
* For root routes with meta.requiresAuth: true, redirects to /login if not authenticated.
|
|
25
24
|
*/
|
|
26
25
|
export function KuckitModuleRoute() {
|
|
27
|
-
const { routeRegistry } =
|
|
28
|
-
const
|
|
26
|
+
const { routeRegistry } = useKuckitWeb()
|
|
27
|
+
const authClient = useAuth()
|
|
29
28
|
const { location } = useRouterState()
|
|
30
29
|
const navigate = useNavigate()
|
|
31
30
|
const pathname = location.pathname
|
|
@@ -47,11 +46,34 @@ export function KuckitModuleRoute() {
|
|
|
47
46
|
// For dashboard context, also try matching with /dashboard prefix stripped
|
|
48
47
|
const modulePathname = isDashboardContext ? pathname.replace('/dashboard', '') || '/' : pathname
|
|
49
48
|
|
|
50
|
-
//
|
|
51
|
-
|
|
49
|
+
// Match route path against pathname, supporting catch-all patterns like /docs/$
|
|
50
|
+
function matchRoute(routePath: string, pathname: string): boolean {
|
|
51
|
+
// Exact match
|
|
52
|
+
if (routePath === pathname) return true
|
|
53
|
+
|
|
54
|
+
// Catch-all pattern: /docs/$ matches /docs/anything/here
|
|
55
|
+
if (routePath.endsWith('/$')) {
|
|
56
|
+
const base = routePath.slice(0, -1) // Remove trailing $
|
|
57
|
+
return pathname.startsWith(base)
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
return false
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// Find matching route - prefer exact matches over catch-all
|
|
64
|
+
const exactMatch = contextRoutes.find((r) => r.path === pathname)
|
|
65
|
+
const catchAllMatch = contextRoutes.find(
|
|
66
|
+
(r) => r.path.endsWith('/$') && matchRoute(r.path, pathname)
|
|
67
|
+
)
|
|
68
|
+
let routeDef = exactMatch || catchAllMatch
|
|
69
|
+
|
|
52
70
|
if (!routeDef && isDashboardContext) {
|
|
53
71
|
// For dashboard routes, also try matching stripped path
|
|
54
|
-
|
|
72
|
+
const exactDash = contextRoutes.find((r) => r.path === modulePathname)
|
|
73
|
+
const catchAllDash = contextRoutes.find(
|
|
74
|
+
(r) => r.path.endsWith('/$') && matchRoute(r.path, modulePathname)
|
|
75
|
+
)
|
|
76
|
+
routeDef = exactDash || catchAllDash
|
|
55
77
|
}
|
|
56
78
|
|
|
57
79
|
// Check auth for root routes with requiresAuth
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { useAuth } from '@kuckit/app-web'
|
|
2
2
|
|
|
3
3
|
export function DashboardOverview() {
|
|
4
|
-
const
|
|
4
|
+
const authClient = useAuth()
|
|
5
5
|
const { data: session } = authClient.useSession()
|
|
6
6
|
|
|
7
7
|
return (
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { ChevronsUpDown, LogOut, User } from 'lucide-react'
|
|
2
2
|
import { useNavigate } from '@tanstack/react-router'
|
|
3
|
-
import {
|
|
3
|
+
import { useAuth } from '@kuckit/app-web'
|
|
4
4
|
import {
|
|
5
5
|
DropdownMenu,
|
|
6
6
|
DropdownMenuContent,
|
|
@@ -19,7 +19,7 @@ import { Avatar, AvatarFallback, AvatarImage } from '@/components/ui/avatar'
|
|
|
19
19
|
export function NavUser() {
|
|
20
20
|
const { isMobile } = useSidebar()
|
|
21
21
|
const navigate = useNavigate()
|
|
22
|
-
const
|
|
22
|
+
const authClient = useAuth()
|
|
23
23
|
const { data: session } = authClient.useSession()
|
|
24
24
|
|
|
25
25
|
const user = session?.user
|
|
@@ -1,27 +1,17 @@
|
|
|
1
|
-
import { createRouter } from '@tanstack/react-router'
|
|
2
1
|
import ReactDOM from 'react-dom/client'
|
|
2
|
+
import { createKuckitWebProvider } from '@kuckit/app-web'
|
|
3
3
|
import { routeTree } from './routeTree.gen'
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
4
|
+
import { Route as rootRoute } from './routes/__root'
|
|
5
|
+
import { getClientModuleSpecs } from './modules.client'
|
|
6
6
|
import './index.css'
|
|
7
7
|
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
8
|
+
const KuckitApp = createKuckitWebProvider({
|
|
9
|
+
serverUrl: import.meta.env.VITE_API_URL || 'http://localhost:3000',
|
|
10
|
+
modules: getClientModuleSpecs(),
|
|
11
|
+
env: import.meta.env.MODE,
|
|
12
|
+
routeTree,
|
|
13
|
+
rootRoute,
|
|
14
|
+
})
|
|
13
15
|
|
|
14
|
-
const
|
|
15
|
-
|
|
16
|
-
if (!rootElement) {
|
|
17
|
-
throw new Error('Root element not found')
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
if (!rootElement.innerHTML) {
|
|
21
|
-
const root = ReactDOM.createRoot(rootElement)
|
|
22
|
-
root.render(
|
|
23
|
-
<ServicesProvider>
|
|
24
|
-
<KuckitProvider />
|
|
25
|
-
</ServicesProvider>
|
|
26
|
-
)
|
|
27
|
-
}
|
|
16
|
+
const rootEl = document.getElementById('app')!
|
|
17
|
+
ReactDOM.createRoot(rootEl).render(<KuckitApp />)
|