create-kuckit-app 0.1.0 → 0.2.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/dist/bin.js +11 -5
- package/dist/{create-project-DTm05G7D.js → create-project-CP-h4Ygi.js} +7 -5
- package/dist/index.js +1 -1
- package/package.json +3 -2
- package/templates/base/.claude/CLAUDE.md +44 -0
- package/templates/base/.claude/agents/daidalos.md +76 -0
- package/templates/base/.claude/agents/episteme.md +79 -0
- package/templates/base/.claude/agents/librarian.md +132 -0
- package/templates/base/.claude/agents/oracle.md +210 -0
- package/templates/base/.claude/commands/create-plan.md +159 -0
- package/templates/base/.claude/commands/file-beads.md +98 -0
- package/templates/base/.claude/commands/review-beads.md +161 -0
- package/templates/base/.claude/settings.json +11 -0
- package/templates/base/.claude/skills/kuckit/SKILL.md +436 -0
- package/templates/base/.claude/skills/kuckit/references/ARCHITECTURE.md +388 -0
- package/templates/base/.claude/skills/kuckit/references/CLI-COMMANDS.md +365 -0
- package/templates/base/.claude/skills/kuckit/references/MODULE-DEVELOPMENT.md +581 -0
- package/templates/base/.claude/skills/kuckit/references/PACKAGES.md +112 -0
- package/templates/base/.claude/skills/kuckit/references/PUBLISHING.md +231 -0
- package/templates/base/.env.example +13 -0
- package/templates/base/.github/workflows/ci.yml +28 -0
- package/templates/base/.husky/pre-commit +1 -0
- package/templates/base/.prettierignore +5 -0
- package/templates/base/.prettierrc +8 -0
- package/templates/base/AGENTS.md +148 -0
- package/templates/base/apps/server/.env.example +18 -0
- package/templates/base/apps/server/AGENTS.md +37 -0
- package/templates/base/apps/server/package.json +13 -4
- package/templates/base/apps/server/src/app.ts +20 -0
- package/templates/base/apps/server/src/auth.ts +10 -0
- package/templates/base/apps/server/src/config/modules.ts +14 -6
- package/templates/base/apps/server/src/container.ts +81 -0
- package/templates/base/apps/server/src/health.ts +27 -0
- package/templates/base/apps/server/src/middleware/container.ts +41 -0
- package/templates/base/apps/server/src/rpc-router-registry.ts +26 -0
- package/templates/base/apps/server/src/rpc.ts +31 -0
- package/templates/base/apps/server/src/server.ts +39 -29
- package/templates/base/apps/web/.env.example +4 -0
- package/templates/base/apps/web/AGENTS.md +53 -0
- package/templates/base/apps/web/index.html +1 -1
- package/templates/base/apps/web/package.json +15 -3
- package/templates/base/apps/web/src/lib/kuckit-router.ts +42 -0
- package/templates/base/apps/web/src/main.tsx +26 -14
- package/templates/base/apps/web/src/providers/KuckitProvider.tsx +147 -0
- package/templates/base/apps/web/src/providers/ServicesProvider.tsx +47 -0
- package/templates/base/apps/web/src/routeTree.gen.ts +91 -0
- package/templates/base/apps/web/src/routes/__root.tsx +31 -0
- package/templates/base/apps/web/src/routes/index.tsx +46 -0
- package/templates/base/apps/web/src/routes/login.tsx +108 -0
- package/templates/base/apps/web/src/services/auth-client.ts +12 -0
- package/templates/base/apps/web/src/services/index.ts +3 -0
- package/templates/base/apps/web/src/services/rpc.ts +29 -0
- package/templates/base/apps/web/src/services/types.ts +14 -0
- package/templates/base/apps/web/vite.config.ts +2 -1
- package/templates/base/docker-compose.yml +23 -0
- package/templates/base/eslint.config.js +18 -0
- package/templates/base/package.json +32 -2
- package/templates/base/packages/api/AGENTS.md +27 -0
- package/templates/base/packages/api/package.json +35 -0
- package/templates/base/packages/api/src/context.ts +48 -0
- package/templates/base/packages/api/src/index.ts +22 -0
- package/templates/base/packages/api/tsconfig.json +8 -0
- package/templates/base/packages/auth/AGENTS.md +45 -0
- package/templates/base/packages/auth/package.json +27 -0
- package/templates/base/packages/auth/src/index.ts +22 -0
- package/templates/base/packages/auth/tsconfig.json +8 -0
- package/templates/base/packages/db/AGENTS.md +59 -0
- package/templates/base/packages/db/drizzle.config.ts +19 -0
- package/templates/base/packages/db/package.json +36 -0
- package/templates/base/packages/db/src/connection.ts +40 -0
- package/templates/base/packages/db/src/index.ts +4 -0
- package/templates/base/packages/db/src/migrations/0000_init.sql +54 -0
- package/templates/base/packages/db/src/migrations/meta/_journal.json +13 -0
- package/templates/base/packages/db/src/schema/auth.ts +51 -0
- package/templates/base/packages/db/tsconfig.json +8 -0
- package/templates/base/packages/items-module/AGENTS.md +112 -0
- package/templates/base/packages/items-module/package.json +32 -0
- package/templates/base/packages/items-module/src/adapters/item.drizzle.ts +66 -0
- package/templates/base/packages/items-module/src/api/items.router.ts +47 -0
- package/templates/base/packages/items-module/src/client-module.ts +39 -0
- package/templates/base/packages/items-module/src/domain/item.entity.ts +36 -0
- package/templates/base/packages/items-module/src/index.ts +15 -0
- package/templates/base/packages/items-module/src/module.ts +53 -0
- package/templates/base/packages/items-module/src/ports/item.repository.ts +13 -0
- package/templates/base/packages/items-module/src/ui/ItemsPage.tsx +162 -0
- package/templates/base/packages/items-module/src/usecases/create-item.ts +25 -0
- package/templates/base/packages/items-module/src/usecases/delete-item.ts +18 -0
- package/templates/base/packages/items-module/src/usecases/get-item.ts +19 -0
- package/templates/base/packages/items-module/src/usecases/list-items.ts +21 -0
- package/templates/base/packages/items-module/tsconfig.json +9 -0
- package/templates/base/turbo.json +13 -1
- package/templates/base/apps/web/src/App.tsx +0 -16
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import type { Express } from 'express'
|
|
2
|
+
import type { AppContainer } from './container'
|
|
3
|
+
|
|
4
|
+
export const setupHealth = (app: Express, container: AppContainer) => {
|
|
5
|
+
app.get('/health', async (_req, res) => {
|
|
6
|
+
const { dbPool } = container.cradle
|
|
7
|
+
|
|
8
|
+
try {
|
|
9
|
+
const client = await dbPool.connect()
|
|
10
|
+
await client.query('SELECT 1')
|
|
11
|
+
client.release()
|
|
12
|
+
|
|
13
|
+
res.status(200).json({
|
|
14
|
+
status: 'healthy',
|
|
15
|
+
timestamp: new Date().toISOString(),
|
|
16
|
+
checks: { database: 'ok' },
|
|
17
|
+
})
|
|
18
|
+
} catch (error) {
|
|
19
|
+
console.error('[Health] Database check failed:', error)
|
|
20
|
+
res.status(503).json({
|
|
21
|
+
status: 'unhealthy',
|
|
22
|
+
timestamp: new Date().toISOString(),
|
|
23
|
+
checks: { database: 'failed' },
|
|
24
|
+
})
|
|
25
|
+
}
|
|
26
|
+
})
|
|
27
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import type { Express, Request, Response, NextFunction } from 'express'
|
|
2
|
+
import { asValue } from 'awilix'
|
|
3
|
+
import { buildRootContainer, type AppContainer } from '../container'
|
|
4
|
+
|
|
5
|
+
let rootContainer: AppContainer
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Initialize and setup container middleware
|
|
9
|
+
* Must be awaited before using the container
|
|
10
|
+
*/
|
|
11
|
+
export const setupContainerMiddleware = async (app: Express): Promise<void> => {
|
|
12
|
+
rootContainer = await buildRootContainer()
|
|
13
|
+
|
|
14
|
+
app.use((req: Request, res: Response, next: NextFunction) => {
|
|
15
|
+
req.scope = rootContainer.createScope()
|
|
16
|
+
|
|
17
|
+
req.scope.register({
|
|
18
|
+
requestId: asValue(crypto.randomUUID()),
|
|
19
|
+
})
|
|
20
|
+
|
|
21
|
+
res.on('finish', () => {
|
|
22
|
+
req.scope?.dispose()
|
|
23
|
+
})
|
|
24
|
+
|
|
25
|
+
res.on('close', () => {
|
|
26
|
+
req.scope?.dispose()
|
|
27
|
+
})
|
|
28
|
+
|
|
29
|
+
next()
|
|
30
|
+
})
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Get root container (for graceful shutdown)
|
|
35
|
+
*/
|
|
36
|
+
export const getRootContainer = (): AppContainer => {
|
|
37
|
+
if (!rootContainer) {
|
|
38
|
+
throw new Error('Container not initialized')
|
|
39
|
+
}
|
|
40
|
+
return rootContainer
|
|
41
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
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
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
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,39 +1,49 @@
|
|
|
1
1
|
import 'dotenv/config'
|
|
2
|
-
import
|
|
3
|
-
import
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
2
|
+
import { createApp } from './app'
|
|
3
|
+
import { setupContainerMiddleware, getRootContainer } from './middleware/container'
|
|
4
|
+
import { setupAuth } from './auth'
|
|
5
|
+
import { setupRPC } from './rpc'
|
|
6
|
+
import { setupHealth } from './health'
|
|
7
|
+
import { disposeContainer } from './container'
|
|
6
8
|
|
|
7
|
-
|
|
8
|
-
|
|
9
|
+
/**
|
|
10
|
+
* Bootstrap and start server
|
|
11
|
+
*/
|
|
12
|
+
const bootstrap = async () => {
|
|
13
|
+
const app = createApp()
|
|
9
14
|
|
|
10
|
-
app
|
|
11
|
-
|
|
15
|
+
await setupContainerMiddleware(app)
|
|
16
|
+
const rootContainer = getRootContainer()
|
|
12
17
|
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
env: process.env.NODE_ENV ?? 'development',
|
|
17
|
-
})
|
|
18
|
+
setupAuth(app)
|
|
19
|
+
setupRPC(app)
|
|
20
|
+
setupHealth(app, rootContainer)
|
|
18
21
|
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
env: process.env.NODE_ENV ?? 'development',
|
|
23
|
-
modules: getModuleSpecs(),
|
|
24
|
-
onComplete: () => {
|
|
25
|
-
console.log('All modules loaded')
|
|
26
|
-
},
|
|
22
|
+
const port = process.env.PORT || 3000
|
|
23
|
+
const server = app.listen(port, () => {
|
|
24
|
+
console.log(`Server is running on port ${port}`)
|
|
27
25
|
})
|
|
28
26
|
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
res.json({ status: 'ok' })
|
|
32
|
-
})
|
|
27
|
+
const shutdown = async () => {
|
|
28
|
+
console.log('Shutting down gracefully...')
|
|
33
29
|
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
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)
|
|
37
44
|
}
|
|
38
45
|
|
|
39
|
-
bootstrap().catch(
|
|
46
|
+
bootstrap().catch((error) => {
|
|
47
|
+
console.error('Failed to start server:', error)
|
|
48
|
+
process.exit(1)
|
|
49
|
+
})
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
# AGENTS.md - Web App
|
|
2
|
+
|
|
3
|
+
> See root [AGENTS.md](../../AGENTS.md) for project overview
|
|
4
|
+
|
|
5
|
+
## Purpose
|
|
6
|
+
|
|
7
|
+
React frontend using TanStack Router, TanStack Query, and Kuckit client modules.
|
|
8
|
+
|
|
9
|
+
## Key Files
|
|
10
|
+
|
|
11
|
+
| File | Purpose |
|
|
12
|
+
| -------------------------------- | --------------------------------- |
|
|
13
|
+
| `main.tsx` | App entry point |
|
|
14
|
+
| `modules.client.ts` | Client module registration |
|
|
15
|
+
| `providers/KuckitProvider.tsx` | Kuckit context setup |
|
|
16
|
+
| `providers/ServicesProvider.tsx` | Services context (RPC, auth) |
|
|
17
|
+
| `routes/` | TanStack Router file-based routes |
|
|
18
|
+
|
|
19
|
+
## Adding Routes
|
|
20
|
+
|
|
21
|
+
Create files in `src/routes/`:
|
|
22
|
+
|
|
23
|
+
- `src/routes/about.tsx` → `/about`
|
|
24
|
+
- `src/routes/users/$id.tsx` → `/users/:id`
|
|
25
|
+
- `src/routes/settings/index.tsx` → `/settings`
|
|
26
|
+
|
|
27
|
+
## Using Module UI Components
|
|
28
|
+
|
|
29
|
+
```tsx
|
|
30
|
+
import { ItemsPage } from '@__APP_NAME_KEBAB__/items-module/ui'
|
|
31
|
+
|
|
32
|
+
// In your route component
|
|
33
|
+
export function Route() {
|
|
34
|
+
return <ItemsPage />
|
|
35
|
+
}
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
## Adding Client Modules
|
|
39
|
+
|
|
40
|
+
1. Import in `modules.client.ts`:
|
|
41
|
+
|
|
42
|
+
```typescript
|
|
43
|
+
import { kuckitClientModule as myClientModule } from '@__APP_NAME_KEBAB__/my-module/client'
|
|
44
|
+
|
|
45
|
+
export const clientModules = [
|
|
46
|
+
itemsClientModule,
|
|
47
|
+
myClientModule, // Add here
|
|
48
|
+
]
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
## Environment Variables
|
|
52
|
+
|
|
53
|
+
See `.env.example` in this directory.
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
{
|
|
2
|
-
"name": "__APP_NAME_KEBAB__
|
|
2
|
+
"name": "@__APP_NAME_KEBAB__/web",
|
|
3
|
+
"version": "0.0.1",
|
|
4
|
+
"private": true,
|
|
3
5
|
"type": "module",
|
|
4
6
|
"scripts": {
|
|
5
7
|
"dev": "vite --port=3001",
|
|
@@ -7,12 +9,22 @@
|
|
|
7
9
|
"check-types": "tsc --noEmit"
|
|
8
10
|
},
|
|
9
11
|
"dependencies": {
|
|
10
|
-
"@kuckit/sdk-react": "^
|
|
12
|
+
"@kuckit/sdk-react": "^1.0.0",
|
|
13
|
+
"@orpc/client": "^1.10.0",
|
|
14
|
+
"@orpc/tanstack-query": "^1.10.0",
|
|
11
15
|
"@tanstack/react-query": "^5.85.5",
|
|
16
|
+
"@tanstack/react-router": "^1.114.25",
|
|
17
|
+
"better-auth": "^1.3.28",
|
|
12
18
|
"react": "^19.1.0",
|
|
13
|
-
"react-dom": "^19.1.0"
|
|
19
|
+
"react-dom": "^19.1.0",
|
|
20
|
+
"zod": "^4.1.11",
|
|
21
|
+
"@__APP_NAME_KEBAB__/auth": "workspace:*"
|
|
14
22
|
},
|
|
15
23
|
"devDependencies": {
|
|
24
|
+
"@tanstack/react-router-devtools": "^1.114.27",
|
|
25
|
+
"@tanstack/router-plugin": "^1.114.27",
|
|
26
|
+
"@tanstack/react-query-devtools": "^5.85.5",
|
|
27
|
+
"@types/node": "^22.13.13",
|
|
16
28
|
"@types/react": "~19.1.10",
|
|
17
29
|
"@types/react-dom": "^19.0.4",
|
|
18
30
|
"@vitejs/plugin-react": "^4.3.4",
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { createRoute, type AnyRoute, type RouteComponent } from '@tanstack/react-router'
|
|
2
|
+
import type { QueryClient } from '@tanstack/react-query'
|
|
3
|
+
import type { RouteRegistry, RouteDefinition } from '@kuckit/sdk-react'
|
|
4
|
+
import type { ORPCUtils } from '../services/types'
|
|
5
|
+
|
|
6
|
+
export interface KuckitRouterContext {
|
|
7
|
+
orpc: ORPCUtils
|
|
8
|
+
queryClient: QueryClient
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Build TanStack Router routes from a RouteRegistry
|
|
13
|
+
*/
|
|
14
|
+
export function buildModuleRoutes(routeRegistry: RouteRegistry, rootRoute: AnyRoute): AnyRoute[] {
|
|
15
|
+
const routes = routeRegistry.getAll()
|
|
16
|
+
const routeMap = new Map<string, AnyRoute>()
|
|
17
|
+
|
|
18
|
+
for (const routeDef of routes) {
|
|
19
|
+
const route = createModuleRoute(routeDef, rootRoute)
|
|
20
|
+
routeMap.set(routeDef.id, route)
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
const topLevelRoutes = routes
|
|
24
|
+
.filter((r) => !r.parentRouteId || r.parentRouteId === '__root__')
|
|
25
|
+
.map((r) => routeMap.get(r.id)!)
|
|
26
|
+
.filter(Boolean)
|
|
27
|
+
|
|
28
|
+
return topLevelRoutes
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function createModuleRoute(routeDef: RouteDefinition, parentRoute: AnyRoute): AnyRoute {
|
|
32
|
+
return createRoute({
|
|
33
|
+
getParentRoute: () => parentRoute,
|
|
34
|
+
path: routeDef.path,
|
|
35
|
+
component: routeDef.component as RouteComponent,
|
|
36
|
+
...(routeDef.meta?.title && {
|
|
37
|
+
head: () => ({
|
|
38
|
+
meta: [{ title: routeDef.meta!.title }],
|
|
39
|
+
}),
|
|
40
|
+
}),
|
|
41
|
+
})
|
|
42
|
+
}
|
|
@@ -1,14 +1,26 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
1
|
+
import { createRouter } from '@tanstack/react-router'
|
|
2
|
+
import ReactDOM from 'react-dom/client'
|
|
3
|
+
import { routeTree } from './routeTree.gen'
|
|
4
|
+
import { ServicesProvider } from './providers/ServicesProvider'
|
|
5
|
+
import { KuckitProvider } from './providers/KuckitProvider'
|
|
6
|
+
|
|
7
|
+
declare module '@tanstack/react-router' {
|
|
8
|
+
interface Register {
|
|
9
|
+
router: ReturnType<typeof createRouter<typeof routeTree>>
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
const rootElement = document.getElementById('app')
|
|
14
|
+
|
|
15
|
+
if (!rootElement) {
|
|
16
|
+
throw new Error('Root element not found')
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
if (!rootElement.innerHTML) {
|
|
20
|
+
const root = ReactDOM.createRoot(rootElement)
|
|
21
|
+
root.render(
|
|
22
|
+
<ServicesProvider>
|
|
23
|
+
<KuckitProvider />
|
|
24
|
+
</ServicesProvider>
|
|
25
|
+
)
|
|
26
|
+
}
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
import { createContext, useContext, useEffect, useState, useMemo, type ReactNode } from 'react'
|
|
2
|
+
import {
|
|
3
|
+
RouterProvider,
|
|
4
|
+
createRouter,
|
|
5
|
+
createRoute,
|
|
6
|
+
type RouteComponent,
|
|
7
|
+
} from '@tanstack/react-router'
|
|
8
|
+
import {
|
|
9
|
+
loadKuckitClientModules,
|
|
10
|
+
KuckitNavProvider,
|
|
11
|
+
KuckitSlotProvider,
|
|
12
|
+
KuckitRpcProvider,
|
|
13
|
+
type LoadClientModulesResult,
|
|
14
|
+
type RouteRegistry,
|
|
15
|
+
type NavRegistry,
|
|
16
|
+
type SlotRegistry,
|
|
17
|
+
} from '@kuckit/sdk-react'
|
|
18
|
+
import { routeTree } from '../routeTree.gen'
|
|
19
|
+
import { Route as rootRoute } from '../routes/__root'
|
|
20
|
+
import { useServices } from './ServicesProvider'
|
|
21
|
+
import { getClientModuleSpecs } from '../modules.client'
|
|
22
|
+
|
|
23
|
+
interface KuckitContextValue {
|
|
24
|
+
routeRegistry: RouteRegistry
|
|
25
|
+
navRegistry: NavRegistry
|
|
26
|
+
slotRegistry: SlotRegistry
|
|
27
|
+
isLoaded: boolean
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
const KuckitContext = createContext<KuckitContextValue | null>(null)
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Hook to access Kuckit registries
|
|
34
|
+
*/
|
|
35
|
+
export function useKuckit(): KuckitContextValue {
|
|
36
|
+
const ctx = useContext(KuckitContext)
|
|
37
|
+
if (!ctx) {
|
|
38
|
+
throw new Error('useKuckit must be used within a KuckitProvider')
|
|
39
|
+
}
|
|
40
|
+
return ctx
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
interface KuckitProviderProps {
|
|
44
|
+
children?: ReactNode
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* KuckitProvider handles module loading and router creation
|
|
49
|
+
*/
|
|
50
|
+
export function KuckitProvider({ children }: KuckitProviderProps) {
|
|
51
|
+
const { orpc, queryClient, rpcClient } = useServices()
|
|
52
|
+
const [loadResult, setLoadResult] = useState<LoadClientModulesResult | null>(null)
|
|
53
|
+
const [error, setError] = useState<Error | null>(null)
|
|
54
|
+
|
|
55
|
+
useEffect(() => {
|
|
56
|
+
let cancelled = false
|
|
57
|
+
|
|
58
|
+
const loadModules = async () => {
|
|
59
|
+
try {
|
|
60
|
+
const result = await loadKuckitClientModules({
|
|
61
|
+
orpc,
|
|
62
|
+
queryClient,
|
|
63
|
+
env: import.meta.env.MODE,
|
|
64
|
+
modules: getClientModuleSpecs(),
|
|
65
|
+
})
|
|
66
|
+
|
|
67
|
+
if (!cancelled) {
|
|
68
|
+
setLoadResult(result)
|
|
69
|
+
}
|
|
70
|
+
} catch (err) {
|
|
71
|
+
if (!cancelled) {
|
|
72
|
+
setError(err instanceof Error ? err : new Error(String(err)))
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
loadModules()
|
|
78
|
+
|
|
79
|
+
return () => {
|
|
80
|
+
cancelled = true
|
|
81
|
+
}
|
|
82
|
+
}, [orpc, queryClient])
|
|
83
|
+
|
|
84
|
+
// Build router with module routes
|
|
85
|
+
const router = useMemo(() => {
|
|
86
|
+
if (!loadResult) return null
|
|
87
|
+
|
|
88
|
+
const moduleRouteDefs = loadResult.routeRegistry.getAll()
|
|
89
|
+
const moduleRoutes = moduleRouteDefs.map((routeDef) =>
|
|
90
|
+
createRoute({
|
|
91
|
+
getParentRoute: () => rootRoute,
|
|
92
|
+
path: routeDef.path,
|
|
93
|
+
component: routeDef.component as RouteComponent,
|
|
94
|
+
})
|
|
95
|
+
)
|
|
96
|
+
|
|
97
|
+
if (moduleRoutes.length > 0) {
|
|
98
|
+
console.log(
|
|
99
|
+
`Loaded ${moduleRoutes.length} module routes:`,
|
|
100
|
+
moduleRouteDefs.map((r) => r.path)
|
|
101
|
+
)
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
return createRouter({
|
|
105
|
+
routeTree,
|
|
106
|
+
defaultPreload: 'intent',
|
|
107
|
+
context: { orpc, queryClient },
|
|
108
|
+
})
|
|
109
|
+
}, [loadResult, orpc, queryClient])
|
|
110
|
+
|
|
111
|
+
if (error) {
|
|
112
|
+
return (
|
|
113
|
+
<div style={{ padding: '2rem', color: 'red' }}>
|
|
114
|
+
<h2>Failed to load modules</h2>
|
|
115
|
+
<p>{error.message}</p>
|
|
116
|
+
</div>
|
|
117
|
+
)
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
if (!loadResult || !router) {
|
|
121
|
+
return (
|
|
122
|
+
<div style={{ padding: '2rem' }}>
|
|
123
|
+
<p>Loading...</p>
|
|
124
|
+
</div>
|
|
125
|
+
)
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
const contextValue: KuckitContextValue = {
|
|
129
|
+
routeRegistry: loadResult.routeRegistry,
|
|
130
|
+
navRegistry: loadResult.navRegistry,
|
|
131
|
+
slotRegistry: loadResult.slotRegistry,
|
|
132
|
+
isLoaded: true,
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
return (
|
|
136
|
+
<KuckitContext.Provider value={contextValue}>
|
|
137
|
+
<KuckitRpcProvider client={rpcClient}>
|
|
138
|
+
<KuckitNavProvider registry={loadResult.navRegistry}>
|
|
139
|
+
<KuckitSlotProvider registry={loadResult.slotRegistry}>
|
|
140
|
+
<RouterProvider router={router} />
|
|
141
|
+
{children}
|
|
142
|
+
</KuckitSlotProvider>
|
|
143
|
+
</KuckitNavProvider>
|
|
144
|
+
</KuckitRpcProvider>
|
|
145
|
+
</KuckitContext.Provider>
|
|
146
|
+
)
|
|
147
|
+
}
|
|
@@ -0,0 +1,47 @@
|
|
|
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
|
+
}
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
/* eslint-disable */
|
|
2
|
+
|
|
3
|
+
// @ts-nocheck
|
|
4
|
+
|
|
5
|
+
// noinspection JSUnusedGlobalSymbols
|
|
6
|
+
|
|
7
|
+
// This file was automatically generated by TanStack Router.
|
|
8
|
+
// You should NOT make any changes in this file as it will be overwritten.
|
|
9
|
+
// Additionally, you should also exclude this file from your linter and/or formatter to prevent it from being checked or modified.
|
|
10
|
+
|
|
11
|
+
// Import Routes
|
|
12
|
+
|
|
13
|
+
import { Route as rootRoute } from './routes/__root'
|
|
14
|
+
import { Route as LoginImport } from './routes/login'
|
|
15
|
+
import { Route as IndexImport } from './routes/index'
|
|
16
|
+
|
|
17
|
+
// Create/Update Routes
|
|
18
|
+
|
|
19
|
+
const LoginRoute = LoginImport.update({
|
|
20
|
+
id: '/login',
|
|
21
|
+
path: '/login',
|
|
22
|
+
getParentRoute: () => rootRoute,
|
|
23
|
+
} as any)
|
|
24
|
+
|
|
25
|
+
const IndexRoute = IndexImport.update({
|
|
26
|
+
id: '/',
|
|
27
|
+
path: '/',
|
|
28
|
+
getParentRoute: () => rootRoute,
|
|
29
|
+
} as any)
|
|
30
|
+
|
|
31
|
+
// Populate the FileRoutesByPath interface
|
|
32
|
+
|
|
33
|
+
declare module '@tanstack/react-router' {
|
|
34
|
+
interface FileRoutesByPath {
|
|
35
|
+
'/': {
|
|
36
|
+
id: '/'
|
|
37
|
+
path: '/'
|
|
38
|
+
fullPath: '/'
|
|
39
|
+
preLoaderRoute: typeof IndexImport
|
|
40
|
+
parentRoute: typeof rootRoute
|
|
41
|
+
}
|
|
42
|
+
'/login': {
|
|
43
|
+
id: '/login'
|
|
44
|
+
path: '/login'
|
|
45
|
+
fullPath: '/login'
|
|
46
|
+
preLoaderRoute: typeof LoginImport
|
|
47
|
+
parentRoute: typeof rootRoute
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// Create and export the route tree
|
|
53
|
+
|
|
54
|
+
export interface FileRoutesByFullPath {
|
|
55
|
+
'/': typeof IndexRoute
|
|
56
|
+
'/login': typeof LoginRoute
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export interface FileRoutesByTo {
|
|
60
|
+
'/': typeof IndexRoute
|
|
61
|
+
'/login': typeof LoginRoute
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export interface FileRoutesById {
|
|
65
|
+
__root__: typeof rootRoute
|
|
66
|
+
'/': typeof IndexRoute
|
|
67
|
+
'/login': typeof LoginRoute
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
export interface FileRouteTypes {
|
|
71
|
+
fileRoutesByFullPath: FileRoutesByFullPath
|
|
72
|
+
fullPaths: '/' | '/login'
|
|
73
|
+
fileRoutesByTo: FileRoutesByTo
|
|
74
|
+
to: '/' | '/login'
|
|
75
|
+
id: '__root__' | '/' | '/login'
|
|
76
|
+
fileRoutesById: FileRoutesById
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
export interface RootRouteChildren {
|
|
80
|
+
IndexRoute: typeof IndexRoute
|
|
81
|
+
LoginRoute: typeof LoginRoute
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
const rootRouteChildren: RootRouteChildren = {
|
|
85
|
+
IndexRoute: IndexRoute,
|
|
86
|
+
LoginRoute: LoginRoute,
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
export const routeTree = rootRoute
|
|
90
|
+
._addFileChildren(rootRouteChildren)
|
|
91
|
+
._addFileTypes<FileRouteTypes>()
|