create-kuckit-app 0.4.0 → 1.0.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 +1 -1
- package/dist/{create-project-geQBZ0Ru.js → create-project-CAsuZMK5.js} +2 -1
- package/dist/index.js +1 -1
- package/package.json +1 -1
- package/templates/base/.claude/CLAUDE.md +83 -0
- package/templates/base/.claude/{commands/file-beads.md → skills/file-beads/SKILL.md} +3 -3
- package/templates/base/.claude/{commands/review-beads.md → skills/review-beads/SKILL.md} +3 -3
- package/templates/base/AGENTS.md +86 -0
- package/templates/base/apps/server/AGENTS.md +42 -85
- package/templates/base/apps/server/package.json +1 -13
- 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 -46
- package/templates/base/apps/web/AGENTS.md +68 -91
- package/templates/base/apps/web/package.json +2 -6
- 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/$.tsx +1 -1
- 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/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/docker-compose.yml +1 -1
- package/templates/base/drizzle.config.ts +2 -6
- package/templates/base/kuckit.config.ts +30 -0
- package/templates/base/package.json +3 -0
- package/templates/base/packages/items-module/AGENTS.md +83 -0
- 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 -83
- 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/module-rest-routes.ts +0 -47
- package/templates/base/apps/server/src/rest-router-registry.ts +0 -32
- 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/components/KuckitModuleRoute.tsx +0 -119
- 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
|
@@ -1,83 +0,0 @@
|
|
|
1
|
-
import type { AwilixContainer } from 'awilix'
|
|
2
|
-
import {
|
|
3
|
-
createKuckitContainer,
|
|
4
|
-
loadKuckitModules,
|
|
5
|
-
type CoreConfig,
|
|
6
|
-
type CoreCradle,
|
|
7
|
-
} from '@kuckit/sdk'
|
|
8
|
-
import type { Pool } from 'pg'
|
|
9
|
-
import { ensureDatabaseUrl } from '@kuckit/db/connection'
|
|
10
|
-
import { getModuleSpecs } from './config/modules'
|
|
11
|
-
import { wireModuleRpcRouters } from './rpc-router-registry'
|
|
12
|
-
import { collectModuleRestRouters } from './rest-router-registry'
|
|
13
|
-
|
|
14
|
-
/**
|
|
15
|
-
* Server configuration
|
|
16
|
-
*/
|
|
17
|
-
export interface ServerConfig extends CoreConfig {
|
|
18
|
-
port: number
|
|
19
|
-
corsOrigin: string
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
/**
|
|
23
|
-
* Server DI container cradle
|
|
24
|
-
*/
|
|
25
|
-
export interface ServerCradle extends CoreCradle {
|
|
26
|
-
config: ServerConfig
|
|
27
|
-
dbPool: Pool
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
export type AppContainer = AwilixContainer<ServerCradle>
|
|
31
|
-
export type { ServerConfig as Config, ServerCradle as Cradle }
|
|
32
|
-
|
|
33
|
-
/**
|
|
34
|
-
* Load configuration from environment
|
|
35
|
-
*/
|
|
36
|
-
const loadConfig = (): ServerConfig => {
|
|
37
|
-
const databaseUrl = ensureDatabaseUrl()
|
|
38
|
-
|
|
39
|
-
return {
|
|
40
|
-
databaseUrl,
|
|
41
|
-
enableFileLogging: process.env.ENABLE_FILE_LOGGING === 'true',
|
|
42
|
-
logDir: process.env.LOG_DIR || './logs',
|
|
43
|
-
logLevel: (process.env.LOG_LEVEL || 'INFO') as 'DEBUG' | 'INFO' | 'WARN' | 'ERROR',
|
|
44
|
-
env: process.env.NODE_ENV || 'development',
|
|
45
|
-
port: parseInt(process.env.PORT || '3000', 10),
|
|
46
|
-
corsOrigin: process.env.CORS_ORIGIN || 'http://localhost:3001',
|
|
47
|
-
}
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
/**
|
|
51
|
-
* Build root container with all modules
|
|
52
|
-
*/
|
|
53
|
-
export const buildRootContainer = async (): Promise<AppContainer> => {
|
|
54
|
-
const config = loadConfig()
|
|
55
|
-
|
|
56
|
-
const container = await createKuckitContainer({ config })
|
|
57
|
-
|
|
58
|
-
await loadKuckitModules({
|
|
59
|
-
container,
|
|
60
|
-
env: config.env,
|
|
61
|
-
modules: getModuleSpecs(),
|
|
62
|
-
onApiRegistrations: (registrations) => {
|
|
63
|
-
wireModuleRpcRouters(registrations)
|
|
64
|
-
collectModuleRestRouters(registrations)
|
|
65
|
-
console.log(`Loaded ${registrations.length} API registrations from modules`)
|
|
66
|
-
},
|
|
67
|
-
onComplete: () => {
|
|
68
|
-
console.log('All modules loaded successfully')
|
|
69
|
-
},
|
|
70
|
-
})
|
|
71
|
-
|
|
72
|
-
return container as AppContainer
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
/**
|
|
76
|
-
* Cleanup container resources
|
|
77
|
-
*/
|
|
78
|
-
export const disposeContainer = async (container: AppContainer): Promise<void> => {
|
|
79
|
-
const { dbPool } = container.cradle
|
|
80
|
-
if (dbPool && typeof dbPool.end === 'function') {
|
|
81
|
-
await dbPool.end()
|
|
82
|
-
}
|
|
83
|
-
}
|
|
@@ -1,27 +0,0 @@
|
|
|
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
|
-
}
|
|
@@ -1,41 +0,0 @@
|
|
|
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
|
-
}
|
|
@@ -1,47 +0,0 @@
|
|
|
1
|
-
import type { Express, Request, Response, NextFunction } from 'express'
|
|
2
|
-
import express from 'express'
|
|
3
|
-
import { asValue } from 'awilix'
|
|
4
|
-
import { fromNodeHeaders } from 'better-auth/node'
|
|
5
|
-
import { auth } from '@kuckit/auth'
|
|
6
|
-
import { getRestRouters } from './rest-router-registry'
|
|
7
|
-
|
|
8
|
-
/**
|
|
9
|
-
* Middleware to add session to request scope for REST routes.
|
|
10
|
-
* Similar to createContext for RPC, but for Express routes.
|
|
11
|
-
*/
|
|
12
|
-
const sessionMiddleware = async (req: Request, _res: Response, next: NextFunction) => {
|
|
13
|
-
if (!req.scope) {
|
|
14
|
-
return next(new Error('Request scope not initialized'))
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
try {
|
|
18
|
-
const session = await auth.api.getSession({
|
|
19
|
-
headers: fromNodeHeaders(req.headers),
|
|
20
|
-
})
|
|
21
|
-
|
|
22
|
-
req.scope.register({
|
|
23
|
-
session: asValue(session),
|
|
24
|
-
})
|
|
25
|
-
|
|
26
|
-
next()
|
|
27
|
-
} catch (error) {
|
|
28
|
-
console.error('[REST Auth] Failed to get session:', error)
|
|
29
|
-
req.scope.register({
|
|
30
|
-
session: asValue(null),
|
|
31
|
-
})
|
|
32
|
-
next()
|
|
33
|
-
}
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
/**
|
|
37
|
-
* Setup REST routers from modules.
|
|
38
|
-
* Must be called after container middleware is initialized.
|
|
39
|
-
*/
|
|
40
|
-
export const setupModuleRestRouters = (app: Express): void => {
|
|
41
|
-
const routers = getRestRouters()
|
|
42
|
-
|
|
43
|
-
for (const { name, router, basePath } of routers) {
|
|
44
|
-
app.use(`/api${basePath}`, express.json(), sessionMiddleware, router)
|
|
45
|
-
console.log(`[REST] Mounted module router: /api${basePath} (${name})`)
|
|
46
|
-
}
|
|
47
|
-
}
|
|
@@ -1,32 +0,0 @@
|
|
|
1
|
-
import type { Router } from 'express'
|
|
2
|
-
import type { ApiRegistration } from '@kuckit/sdk'
|
|
3
|
-
|
|
4
|
-
interface RestRouterEntry {
|
|
5
|
-
name: string
|
|
6
|
-
router: Router
|
|
7
|
-
basePath: string
|
|
8
|
-
}
|
|
9
|
-
|
|
10
|
-
const restRouters: RestRouterEntry[] = []
|
|
11
|
-
|
|
12
|
-
/**
|
|
13
|
-
* Collect REST routers from module registrations.
|
|
14
|
-
* Called from the SDK loader's onApiRegistrations callback.
|
|
15
|
-
*/
|
|
16
|
-
export const collectModuleRestRouters = (registrations: ApiRegistration[]): void => {
|
|
17
|
-
for (const reg of registrations) {
|
|
18
|
-
if (reg.type !== 'rest-router') continue
|
|
19
|
-
|
|
20
|
-
const basePath = reg.prefix ?? `/${reg.name}`
|
|
21
|
-
restRouters.push({
|
|
22
|
-
name: reg.name,
|
|
23
|
-
router: reg.router as Router,
|
|
24
|
-
basePath,
|
|
25
|
-
})
|
|
26
|
-
}
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
/**
|
|
30
|
-
* Get all collected REST routers for wiring to Express app.
|
|
31
|
-
*/
|
|
32
|
-
export const getRestRouters = (): readonly RestRouterEntry[] => restRouters
|
|
@@ -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 '@kuckit/api/context'
|
|
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,119 +0,0 @@
|
|
|
1
|
-
import { useRouterState, Link, useNavigate } from '@tanstack/react-router'
|
|
2
|
-
import { useKuckit } from '@/providers/KuckitProvider'
|
|
3
|
-
import { useServices } from '@/providers/ServicesProvider'
|
|
4
|
-
import { useEffect, useState } from 'react'
|
|
5
|
-
|
|
6
|
-
/**
|
|
7
|
-
* Helper to determine if a route path is a root-level route (outside dashboard).
|
|
8
|
-
* Root routes start with '/' but NOT '/dashboard'.
|
|
9
|
-
*/
|
|
10
|
-
function isRootLevelRoute(path: string): boolean {
|
|
11
|
-
return path.startsWith('/') && !path.startsWith('/dashboard')
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
/**
|
|
15
|
-
* Dynamic route renderer for Kuckit module routes.
|
|
16
|
-
*
|
|
17
|
-
* This component looks up the current path in the RouteRegistry
|
|
18
|
-
* and renders the corresponding module component if found.
|
|
19
|
-
*
|
|
20
|
-
* Route resolution is context-aware:
|
|
21
|
-
* - When rendered under /dashboard/*: matches dashboard routes
|
|
22
|
-
* - When rendered at root level: matches root-level routes (paths starting with '/' but not '/dashboard/')
|
|
23
|
-
*
|
|
24
|
-
* For root routes with meta.requiresAuth: true, redirects to /login if not authenticated.
|
|
25
|
-
*/
|
|
26
|
-
export function KuckitModuleRoute() {
|
|
27
|
-
const { routeRegistry } = useKuckit()
|
|
28
|
-
const { authClient } = useServices()
|
|
29
|
-
const { location } = useRouterState()
|
|
30
|
-
const navigate = useNavigate()
|
|
31
|
-
const pathname = location.pathname
|
|
32
|
-
|
|
33
|
-
const [authChecked, setAuthChecked] = useState(false)
|
|
34
|
-
const [isAuthenticated, setIsAuthenticated] = useState(false)
|
|
35
|
-
|
|
36
|
-
// Determine if we're in dashboard context
|
|
37
|
-
const isDashboardContext = pathname.startsWith('/dashboard')
|
|
38
|
-
|
|
39
|
-
// Filter routes based on context
|
|
40
|
-
const contextRoutes = routeRegistry.getAll().filter((r) => {
|
|
41
|
-
const isRoot = isRootLevelRoute(r.path)
|
|
42
|
-
// In dashboard context: show non-root routes
|
|
43
|
-
// In root context: show root routes
|
|
44
|
-
return isDashboardContext ? !isRoot : isRoot
|
|
45
|
-
})
|
|
46
|
-
|
|
47
|
-
// For dashboard context, also try matching with /dashboard prefix stripped
|
|
48
|
-
const modulePathname = isDashboardContext ? pathname.replace('/dashboard', '') || '/' : pathname
|
|
49
|
-
|
|
50
|
-
// Find matching route
|
|
51
|
-
let routeDef = contextRoutes.find((r) => r.path === pathname)
|
|
52
|
-
if (!routeDef && isDashboardContext) {
|
|
53
|
-
// For dashboard routes, also try matching stripped path
|
|
54
|
-
routeDef = contextRoutes.find((r) => r.path === modulePathname)
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
// Check auth for root routes with requiresAuth
|
|
58
|
-
useEffect(() => {
|
|
59
|
-
async function checkAuth() {
|
|
60
|
-
if (!routeDef) {
|
|
61
|
-
setAuthChecked(true)
|
|
62
|
-
return
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
// Dashboard routes are already protected by the dashboard layout
|
|
66
|
-
if (isDashboardContext) {
|
|
67
|
-
setAuthChecked(true)
|
|
68
|
-
setIsAuthenticated(true)
|
|
69
|
-
return
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
// For root routes, check if auth is required
|
|
73
|
-
if (routeDef.meta?.requiresAuth) {
|
|
74
|
-
const session = await authClient.getSession()
|
|
75
|
-
if (!session.data) {
|
|
76
|
-
// Redirect to login with return URL
|
|
77
|
-
navigate({
|
|
78
|
-
to: '/login',
|
|
79
|
-
search: { redirect: pathname },
|
|
80
|
-
})
|
|
81
|
-
return
|
|
82
|
-
}
|
|
83
|
-
setIsAuthenticated(true)
|
|
84
|
-
}
|
|
85
|
-
setAuthChecked(true)
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
checkAuth()
|
|
89
|
-
}, [routeDef, isDashboardContext, pathname, navigate, authClient])
|
|
90
|
-
|
|
91
|
-
// Show loading while checking auth for protected root routes
|
|
92
|
-
if (!isDashboardContext && routeDef?.meta?.requiresAuth && !authChecked) {
|
|
93
|
-
return (
|
|
94
|
-
<div className="flex items-center justify-center min-h-screen">
|
|
95
|
-
<div className="animate-spin rounded-full h-8 w-8 border-b-2 border-primary" />
|
|
96
|
-
</div>
|
|
97
|
-
)
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
// Protected root route - waiting for redirect
|
|
101
|
-
if (!isDashboardContext && routeDef?.meta?.requiresAuth && !isAuthenticated) {
|
|
102
|
-
return null
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
if (!routeDef) {
|
|
106
|
-
return (
|
|
107
|
-
<div className="flex flex-col items-center justify-center min-h-[50vh] gap-4">
|
|
108
|
-
<h1 className="text-2xl font-bold">Page Not Found</h1>
|
|
109
|
-
<p className="text-muted-foreground">The page "{pathname}" could not be found.</p>
|
|
110
|
-
<Link to="/" className="text-primary hover:underline">
|
|
111
|
-
Go Home
|
|
112
|
-
</Link>
|
|
113
|
-
</div>
|
|
114
|
-
)
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
const Component = routeDef.component as React.ComponentType
|
|
118
|
-
return <Component />
|
|
119
|
-
}
|
|
@@ -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 '@kuckit/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,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
|
-
}
|