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
|
@@ -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,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,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
|
-
})
|