create-kuckit-app 0.1.1 → 0.2.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-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 +351 -0
- package/templates/base/apps/server/.env.example +18 -0
- package/templates/base/apps/server/AGENTS.md +93 -0
- package/templates/base/apps/server/package.json +13 -2
- 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 +22 -0
- 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 +42 -14
- package/templates/base/apps/web/.env.example +4 -0
- package/templates/base/apps/web/AGENTS.md +127 -0
- package/templates/base/apps/web/index.html +1 -1
- package/templates/base/apps/web/package.json +15 -2
- package/templates/base/apps/web/src/components/KuckitModuleRoute.tsx +82 -0
- 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/modules.client.ts +4 -3
- 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/$.tsx +14 -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/tsconfig.json +5 -1
- package/templates/base/apps/web/vite.config.ts +8 -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 +66 -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 +61 -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 +74 -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 +210 -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 +144 -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,29 @@
|
|
|
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
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
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,6 +1,13 @@
|
|
|
1
1
|
import { defineConfig } from 'vite'
|
|
2
2
|
import react from '@vitejs/plugin-react'
|
|
3
|
+
import { TanStackRouterVite } from '@tanstack/router-plugin/vite'
|
|
4
|
+
import path from 'path'
|
|
3
5
|
|
|
4
6
|
export default defineConfig({
|
|
5
|
-
plugins: [react()],
|
|
7
|
+
plugins: [TanStackRouterVite(), react()],
|
|
8
|
+
resolve: {
|
|
9
|
+
alias: {
|
|
10
|
+
'@': path.resolve(__dirname, './src'),
|
|
11
|
+
},
|
|
12
|
+
},
|
|
6
13
|
})
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
name: __APP_NAME_KEBAB__
|
|
2
|
+
|
|
3
|
+
services:
|
|
4
|
+
postgres:
|
|
5
|
+
image: postgres
|
|
6
|
+
container_name: __APP_NAME_KEBAB__-postgres
|
|
7
|
+
environment:
|
|
8
|
+
POSTGRES_DB: __APP_NAME_KEBAB__
|
|
9
|
+
POSTGRES_USER: postgres
|
|
10
|
+
POSTGRES_PASSWORD: password
|
|
11
|
+
ports:
|
|
12
|
+
- '5432:5432'
|
|
13
|
+
volumes:
|
|
14
|
+
- __APP_NAME_KEBAB___postgres_data:/var/lib/postgresql/data
|
|
15
|
+
healthcheck:
|
|
16
|
+
test: ['CMD-SHELL', 'pg_isready -U postgres']
|
|
17
|
+
interval: 10s
|
|
18
|
+
timeout: 5s
|
|
19
|
+
retries: 5
|
|
20
|
+
restart: unless-stopped
|
|
21
|
+
|
|
22
|
+
volumes:
|
|
23
|
+
__APP_NAME_KEBAB___postgres_data:
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import js from '@eslint/js'
|
|
2
|
+
import tseslint from 'typescript-eslint'
|
|
3
|
+
import prettierConfig from 'eslint-config-prettier'
|
|
4
|
+
|
|
5
|
+
export default [
|
|
6
|
+
{
|
|
7
|
+
ignores: ['**/node_modules', '**/dist', '**/.turbo', '**/routeTree.gen.ts'],
|
|
8
|
+
},
|
|
9
|
+
js.configs.recommended,
|
|
10
|
+
...tseslint.configs.recommended,
|
|
11
|
+
{
|
|
12
|
+
rules: {
|
|
13
|
+
'@typescript-eslint/no-explicit-any': 'warn',
|
|
14
|
+
'@typescript-eslint/no-unused-vars': ['error', { argsIgnorePattern: '^_' }],
|
|
15
|
+
},
|
|
16
|
+
},
|
|
17
|
+
prettierConfig,
|
|
18
|
+
]
|
|
@@ -9,11 +9,41 @@
|
|
|
9
9
|
"scripts": {
|
|
10
10
|
"dev": "turbo dev",
|
|
11
11
|
"build": "turbo build",
|
|
12
|
-
"check-types": "turbo check-types"
|
|
12
|
+
"check-types": "turbo check-types",
|
|
13
|
+
"lint": "eslint apps packages",
|
|
14
|
+
"lint:fix": "eslint apps packages --fix",
|
|
15
|
+
"format": "prettier --write \"**/*.{ts,tsx,js,jsx,json,md,yml,yaml}\"",
|
|
16
|
+
"format:check": "prettier --check \"**/*.{ts,tsx,js,jsx,json,md,yml,yaml}\"",
|
|
17
|
+
"db:generate": "turbo -F @__APP_NAME_KEBAB__/db db:generate",
|
|
18
|
+
"db:migrate": "turbo -F @__APP_NAME_KEBAB__/db db:migrate",
|
|
19
|
+
"db:studio": "turbo -F @__APP_NAME_KEBAB__/db db:studio",
|
|
20
|
+
"prepare": "husky"
|
|
21
|
+
},
|
|
22
|
+
"lint-staged": {
|
|
23
|
+
"*.{ts,tsx,js,jsx}": [
|
|
24
|
+
"eslint --max-warnings=0",
|
|
25
|
+
"prettier --write"
|
|
26
|
+
],
|
|
27
|
+
"*.{json,md,yml,yaml}": [
|
|
28
|
+
"prettier --write"
|
|
29
|
+
]
|
|
30
|
+
},
|
|
31
|
+
"dependencies": {
|
|
32
|
+
"@kuckit/sdk": "^1.0.0",
|
|
33
|
+
"awilix": "^12.0.5",
|
|
34
|
+
"dotenv": "^17.2.2",
|
|
35
|
+
"zod": "^4.1.11"
|
|
13
36
|
},
|
|
14
37
|
"devDependencies": {
|
|
38
|
+
"@eslint/js": "^9.17.0",
|
|
39
|
+
"eslint": "^9.17.0",
|
|
40
|
+
"eslint-config-prettier": "^10.0.1",
|
|
41
|
+
"husky": "^9.1.7",
|
|
42
|
+
"lint-staged": "^16.0.0",
|
|
43
|
+
"prettier": "^3.4.2",
|
|
15
44
|
"turbo": "^2.5.4",
|
|
16
|
-
"typescript": "^5.8.2"
|
|
45
|
+
"typescript": "^5.8.2",
|
|
46
|
+
"typescript-eslint": "^8.18.2"
|
|
17
47
|
},
|
|
18
48
|
"packageManager": "bun@1.2.21"
|
|
19
49
|
}
|
|
@@ -0,0 +1,66 @@
|
|
|
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
|
|
@@ -0,0 +1,35 @@
|
|
|
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
|
+
}
|
|
@@ -0,0 +1,48 @@
|
|
|
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>>
|
|
@@ -0,0 +1,22 @@
|
|
|
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'
|
|
@@ -0,0 +1,61 @@
|
|
|
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
|
|
@@ -0,0 +1,27 @@
|
|
|
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
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
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
|
+
})
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
# AGENTS.md - Database Package
|
|
2
|
+
|
|
3
|
+
> See root [AGENTS.md](../../AGENTS.md) for SDK Module System patterns
|
|
4
|
+
|
|
5
|
+
## Purpose
|
|
6
|
+
|
|
7
|
+
Drizzle ORM configuration, database connection, migrations, and shared schema.
|
|
8
|
+
|
|
9
|
+
## Key Files
|
|
10
|
+
|
|
11
|
+
| File | Purpose |
|
|
12
|
+
| ------------------- | ------------------------- |
|
|
13
|
+
| `drizzle.config.ts` | Drizzle Kit configuration |
|
|
14
|
+
| `src/connection.ts` | Database connection pool |
|
|
15
|
+
| `src/schema/` | Shared schema definitions |
|
|
16
|
+
| `src/migrations/` | SQL migration files |
|
|
17
|
+
|
|
18
|
+
## Commands
|
|
19
|
+
|
|
20
|
+
```bash
|
|
21
|
+
# Generate migration from schema changes
|
|
22
|
+
bun run db:generate
|
|
23
|
+
|
|
24
|
+
# Apply pending migrations
|
|
25
|
+
bun run db:migrate
|
|
26
|
+
|
|
27
|
+
# Open Drizzle Studio (database GUI)
|
|
28
|
+
bun run db:studio
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
## Adding a New Table
|
|
32
|
+
|
|
33
|
+
1. Create schema file in `src/schema/` or in your module's `adapters/`:
|
|
34
|
+
|
|
35
|
+
```typescript
|
|
36
|
+
import { pgTable, text, timestamp, uuid } from 'drizzle-orm/pg-core'
|
|
37
|
+
|
|
38
|
+
export const myTable = pgTable('my_table', {
|
|
39
|
+
id: uuid('id').primaryKey().defaultRandom(),
|
|
40
|
+
name: text('name').notNull(),
|
|
41
|
+
createdAt: timestamp('created_at').defaultNow(),
|
|
42
|
+
})
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
2. Export from `src/index.ts` if shared
|
|
46
|
+
|
|
47
|
+
3. Generate and run migration:
|
|
48
|
+
```bash
|
|
49
|
+
bun run db:generate
|
|
50
|
+
bun run db:migrate
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
## Module Schema Pattern
|
|
54
|
+
|
|
55
|
+
Modules can define their own schemas in `adapters/`:
|
|
56
|
+
|
|
57
|
+
```typescript
|
|
58
|
+
// packages/items-module/src/adapters/items.schema.ts
|
|
59
|
+
export const itemsTable = pgTable('items', {
|
|
60
|
+
id: uuid('id').primaryKey().defaultRandom(),
|
|
61
|
+
userId: text('user_id').notNull(),
|
|
62
|
+
name: text('name').notNull(),
|
|
63
|
+
})
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
The schema is imported by `drizzle.config.ts` for migration generation.
|
|
67
|
+
|
|
68
|
+
## Connection
|
|
69
|
+
|
|
70
|
+
```typescript
|
|
71
|
+
import { db } from '@__APP_NAME_KEBAB__/db'
|
|
72
|
+
|
|
73
|
+
const users = await db.select().from(usersTable)
|
|
74
|
+
```
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { defineConfig } from 'drizzle-kit'
|
|
2
|
+
import dotenv from 'dotenv'
|
|
3
|
+
import { dirname, resolve } from 'path'
|
|
4
|
+
import { fileURLToPath } from 'url'
|
|
5
|
+
import { buildDatabaseUrl } from './src/connection'
|
|
6
|
+
|
|
7
|
+
const currentFilePath = fileURLToPath(import.meta.url)
|
|
8
|
+
const currentDirPath = dirname(currentFilePath)
|
|
9
|
+
|
|
10
|
+
dotenv.config({
|
|
11
|
+
path: resolve(currentDirPath, '../../apps/server/.env'),
|
|
12
|
+
})
|
|
13
|
+
|
|
14
|
+
export default defineConfig({
|
|
15
|
+
schema: [resolve(currentDirPath, './src/schema')],
|
|
16
|
+
out: resolve(currentDirPath, './src/migrations'),
|
|
17
|
+
dialect: 'postgresql',
|
|
18
|
+
dbCredentials: { url: buildDatabaseUrl() },
|
|
19
|
+
})
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@__APP_NAME_KEBAB__/db",
|
|
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
|
+
"scripts": {
|
|
19
|
+
"db:generate": "drizzle-kit generate",
|
|
20
|
+
"db:migrate": "drizzle-kit migrate",
|
|
21
|
+
"db:studio": "drizzle-kit studio"
|
|
22
|
+
},
|
|
23
|
+
"devDependencies": {
|
|
24
|
+
"drizzle-kit": "^0.31.2",
|
|
25
|
+
"@types/pg": "^8.11.11"
|
|
26
|
+
},
|
|
27
|
+
"peerDependencies": {
|
|
28
|
+
"typescript": "^5"
|
|
29
|
+
},
|
|
30
|
+
"dependencies": {
|
|
31
|
+
"drizzle-orm": "^0.44.2",
|
|
32
|
+
"pg": "^8.14.1",
|
|
33
|
+
"dotenv": "^17.2.2",
|
|
34
|
+
"zod": "^4.1.11"
|
|
35
|
+
}
|
|
36
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Database connection utilities
|
|
3
|
+
* Single source of truth for DATABASE_URL construction
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Build DATABASE_URL from environment variables
|
|
8
|
+
*
|
|
9
|
+
* Supports two modes:
|
|
10
|
+
* 1. Direct DATABASE_URL - used in local development
|
|
11
|
+
* 2. Individual DB_* vars - used in Cloud Run with Cloud SQL Auth Proxy
|
|
12
|
+
*
|
|
13
|
+
* @throws Error if neither DATABASE_URL nor complete DB_* vars are provided
|
|
14
|
+
*/
|
|
15
|
+
export function buildDatabaseUrl(): string {
|
|
16
|
+
// Prefer DATABASE_URL if provided directly
|
|
17
|
+
if (process.env.DATABASE_URL) {
|
|
18
|
+
return process.env.DATABASE_URL
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
// Construct from individual components (Cloud Run with Cloud SQL)
|
|
22
|
+
const { DB_HOST, DB_USER, DB_PASSWORD, DB_NAME } = process.env
|
|
23
|
+
if (DB_HOST && DB_USER && DB_PASSWORD && DB_NAME) {
|
|
24
|
+
return `postgresql://${DB_USER}:${encodeURIComponent(DB_PASSWORD)}@/${DB_NAME}?host=${DB_HOST}`
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
throw new Error(
|
|
28
|
+
'Missing database configuration: provide DATABASE_URL or DB_HOST, DB_USER, DB_PASSWORD, DB_NAME'
|
|
29
|
+
)
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Ensure DATABASE_URL is set in process.env
|
|
34
|
+
* Call this early in application startup to set up the environment
|
|
35
|
+
*/
|
|
36
|
+
export function ensureDatabaseUrl(): string {
|
|
37
|
+
const url = buildDatabaseUrl()
|
|
38
|
+
process.env.DATABASE_URL = url
|
|
39
|
+
return url
|
|
40
|
+
}
|