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.
Files changed (96) hide show
  1. package/dist/bin.js +1 -1
  2. package/dist/{create-project-DTm05G7D.js → create-project-CP-h4Ygi.js} +7 -5
  3. package/dist/index.js +1 -1
  4. package/package.json +3 -2
  5. package/templates/base/.claude/CLAUDE.md +44 -0
  6. package/templates/base/.claude/agents/daidalos.md +76 -0
  7. package/templates/base/.claude/agents/episteme.md +79 -0
  8. package/templates/base/.claude/agents/librarian.md +132 -0
  9. package/templates/base/.claude/agents/oracle.md +210 -0
  10. package/templates/base/.claude/commands/create-plan.md +159 -0
  11. package/templates/base/.claude/commands/file-beads.md +98 -0
  12. package/templates/base/.claude/commands/review-beads.md +161 -0
  13. package/templates/base/.claude/settings.json +11 -0
  14. package/templates/base/.claude/skills/kuckit/SKILL.md +436 -0
  15. package/templates/base/.claude/skills/kuckit/references/ARCHITECTURE.md +388 -0
  16. package/templates/base/.claude/skills/kuckit/references/CLI-COMMANDS.md +365 -0
  17. package/templates/base/.claude/skills/kuckit/references/MODULE-DEVELOPMENT.md +581 -0
  18. package/templates/base/.claude/skills/kuckit/references/PACKAGES.md +112 -0
  19. package/templates/base/.claude/skills/kuckit/references/PUBLISHING.md +231 -0
  20. package/templates/base/.env.example +13 -0
  21. package/templates/base/.github/workflows/ci.yml +28 -0
  22. package/templates/base/.husky/pre-commit +1 -0
  23. package/templates/base/.prettierignore +5 -0
  24. package/templates/base/.prettierrc +8 -0
  25. package/templates/base/AGENTS.md +351 -0
  26. package/templates/base/apps/server/.env.example +18 -0
  27. package/templates/base/apps/server/AGENTS.md +93 -0
  28. package/templates/base/apps/server/package.json +13 -2
  29. package/templates/base/apps/server/src/app.ts +20 -0
  30. package/templates/base/apps/server/src/auth.ts +10 -0
  31. package/templates/base/apps/server/src/config/modules.ts +22 -0
  32. package/templates/base/apps/server/src/container.ts +81 -0
  33. package/templates/base/apps/server/src/health.ts +27 -0
  34. package/templates/base/apps/server/src/middleware/container.ts +41 -0
  35. package/templates/base/apps/server/src/rpc-router-registry.ts +26 -0
  36. package/templates/base/apps/server/src/rpc.ts +31 -0
  37. package/templates/base/apps/server/src/server.ts +42 -14
  38. package/templates/base/apps/web/.env.example +4 -0
  39. package/templates/base/apps/web/AGENTS.md +127 -0
  40. package/templates/base/apps/web/index.html +1 -1
  41. package/templates/base/apps/web/package.json +15 -2
  42. package/templates/base/apps/web/src/components/KuckitModuleRoute.tsx +82 -0
  43. package/templates/base/apps/web/src/lib/kuckit-router.ts +42 -0
  44. package/templates/base/apps/web/src/main.tsx +26 -14
  45. package/templates/base/apps/web/src/modules.client.ts +4 -3
  46. package/templates/base/apps/web/src/providers/KuckitProvider.tsx +147 -0
  47. package/templates/base/apps/web/src/providers/ServicesProvider.tsx +47 -0
  48. package/templates/base/apps/web/src/routeTree.gen.ts +91 -0
  49. package/templates/base/apps/web/src/routes/$.tsx +14 -0
  50. package/templates/base/apps/web/src/routes/__root.tsx +31 -0
  51. package/templates/base/apps/web/src/routes/index.tsx +46 -0
  52. package/templates/base/apps/web/src/routes/login.tsx +108 -0
  53. package/templates/base/apps/web/src/services/auth-client.ts +12 -0
  54. package/templates/base/apps/web/src/services/index.ts +3 -0
  55. package/templates/base/apps/web/src/services/rpc.ts +29 -0
  56. package/templates/base/apps/web/src/services/types.ts +14 -0
  57. package/templates/base/apps/web/tsconfig.json +5 -1
  58. package/templates/base/apps/web/vite.config.ts +8 -1
  59. package/templates/base/docker-compose.yml +23 -0
  60. package/templates/base/eslint.config.js +18 -0
  61. package/templates/base/package.json +32 -2
  62. package/templates/base/packages/api/AGENTS.md +66 -0
  63. package/templates/base/packages/api/package.json +35 -0
  64. package/templates/base/packages/api/src/context.ts +48 -0
  65. package/templates/base/packages/api/src/index.ts +22 -0
  66. package/templates/base/packages/api/tsconfig.json +8 -0
  67. package/templates/base/packages/auth/AGENTS.md +61 -0
  68. package/templates/base/packages/auth/package.json +27 -0
  69. package/templates/base/packages/auth/src/index.ts +22 -0
  70. package/templates/base/packages/auth/tsconfig.json +8 -0
  71. package/templates/base/packages/db/AGENTS.md +74 -0
  72. package/templates/base/packages/db/drizzle.config.ts +19 -0
  73. package/templates/base/packages/db/package.json +36 -0
  74. package/templates/base/packages/db/src/connection.ts +40 -0
  75. package/templates/base/packages/db/src/index.ts +4 -0
  76. package/templates/base/packages/db/src/migrations/0000_init.sql +54 -0
  77. package/templates/base/packages/db/src/migrations/meta/_journal.json +13 -0
  78. package/templates/base/packages/db/src/schema/auth.ts +51 -0
  79. package/templates/base/packages/db/tsconfig.json +8 -0
  80. package/templates/base/packages/items-module/AGENTS.md +210 -0
  81. package/templates/base/packages/items-module/package.json +32 -0
  82. package/templates/base/packages/items-module/src/adapters/item.drizzle.ts +66 -0
  83. package/templates/base/packages/items-module/src/api/items.router.ts +47 -0
  84. package/templates/base/packages/items-module/src/client-module.ts +39 -0
  85. package/templates/base/packages/items-module/src/domain/item.entity.ts +36 -0
  86. package/templates/base/packages/items-module/src/index.ts +15 -0
  87. package/templates/base/packages/items-module/src/module.ts +53 -0
  88. package/templates/base/packages/items-module/src/ports/item.repository.ts +13 -0
  89. package/templates/base/packages/items-module/src/ui/ItemsPage.tsx +144 -0
  90. package/templates/base/packages/items-module/src/usecases/create-item.ts +25 -0
  91. package/templates/base/packages/items-module/src/usecases/delete-item.ts +18 -0
  92. package/templates/base/packages/items-module/src/usecases/get-item.ts +19 -0
  93. package/templates/base/packages/items-module/src/usecases/list-items.ts +21 -0
  94. package/templates/base/packages/items-module/tsconfig.json +9 -0
  95. package/templates/base/turbo.json +13 -1
  96. 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
+ }
@@ -3,7 +3,11 @@
3
3
  "compilerOptions": {
4
4
  "lib": ["DOM", "DOM.Iterable", "ESNext"],
5
5
  "jsx": "react-jsx",
6
- "noEmit": true
6
+ "noEmit": true,
7
+ "baseUrl": ".",
8
+ "paths": {
9
+ "@/*": ["./src/*"]
10
+ }
7
11
  },
8
12
  "include": ["src"]
9
13
  }
@@ -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,8 @@
1
+ {
2
+ "extends": "../../tsconfig.base.json",
3
+ "compilerOptions": {
4
+ "outDir": "dist",
5
+ "rootDir": "src"
6
+ },
7
+ "include": ["src/**/*"]
8
+ }
@@ -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,8 @@
1
+ {
2
+ "extends": "../../tsconfig.base.json",
3
+ "compilerOptions": {
4
+ "outDir": "dist",
5
+ "rootDir": "src"
6
+ },
7
+ "include": ["src/**/*"]
8
+ }
@@ -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
+ }
@@ -0,0 +1,4 @@
1
+ import { drizzle } from 'drizzle-orm/node-postgres'
2
+ import { buildDatabaseUrl } from './connection'
3
+
4
+ export const db = drizzle(buildDatabaseUrl())