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,54 @@
1
+ -- Initial migration: Auth tables
2
+ -- This migration is generated by drizzle-kit
3
+ -- Run: bun run db:migrate
4
+
5
+ CREATE TABLE IF NOT EXISTS "user" (
6
+ "id" text PRIMARY KEY NOT NULL,
7
+ "name" text NOT NULL,
8
+ "email" text NOT NULL,
9
+ "email_verified" boolean NOT NULL,
10
+ "image" text,
11
+ "created_at" timestamp NOT NULL,
12
+ "updated_at" timestamp NOT NULL,
13
+ CONSTRAINT "user_email_unique" UNIQUE("email")
14
+ );
15
+
16
+ CREATE TABLE IF NOT EXISTS "session" (
17
+ "id" text PRIMARY KEY NOT NULL,
18
+ "expires_at" timestamp NOT NULL,
19
+ "token" text NOT NULL,
20
+ "created_at" timestamp NOT NULL,
21
+ "updated_at" timestamp NOT NULL,
22
+ "ip_address" text,
23
+ "user_agent" text,
24
+ "user_id" text NOT NULL,
25
+ CONSTRAINT "session_token_unique" UNIQUE("token")
26
+ );
27
+
28
+ CREATE TABLE IF NOT EXISTS "account" (
29
+ "id" text PRIMARY KEY NOT NULL,
30
+ "account_id" text NOT NULL,
31
+ "provider_id" text NOT NULL,
32
+ "user_id" text NOT NULL,
33
+ "access_token" text,
34
+ "refresh_token" text,
35
+ "id_token" text,
36
+ "access_token_expires_at" timestamp,
37
+ "refresh_token_expires_at" timestamp,
38
+ "scope" text,
39
+ "password" text,
40
+ "created_at" timestamp NOT NULL,
41
+ "updated_at" timestamp NOT NULL
42
+ );
43
+
44
+ CREATE TABLE IF NOT EXISTS "verification" (
45
+ "id" text PRIMARY KEY NOT NULL,
46
+ "identifier" text NOT NULL,
47
+ "value" text NOT NULL,
48
+ "expires_at" timestamp NOT NULL,
49
+ "created_at" timestamp,
50
+ "updated_at" timestamp
51
+ );
52
+
53
+ ALTER TABLE "session" ADD CONSTRAINT "session_user_id_user_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."user"("id") ON DELETE cascade ON UPDATE no action;
54
+ ALTER TABLE "account" ADD CONSTRAINT "account_user_id_user_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."user"("id") ON DELETE cascade ON UPDATE no action;
@@ -0,0 +1,13 @@
1
+ {
2
+ "version": "7",
3
+ "dialect": "postgresql",
4
+ "entries": [
5
+ {
6
+ "idx": 0,
7
+ "version": "7",
8
+ "when": 1734000000000,
9
+ "tag": "0000_init",
10
+ "breakpoints": true
11
+ }
12
+ ]
13
+ }
@@ -0,0 +1,51 @@
1
+ import { pgTable, text, timestamp, boolean } from 'drizzle-orm/pg-core'
2
+
3
+ export const user = pgTable('user', {
4
+ id: text('id').primaryKey(),
5
+ name: text('name').notNull(),
6
+ email: text('email').notNull().unique(),
7
+ emailVerified: boolean('email_verified').notNull(),
8
+ image: text('image'),
9
+ createdAt: timestamp('created_at').notNull(),
10
+ updatedAt: timestamp('updated_at').notNull(),
11
+ })
12
+
13
+ export const session = pgTable('session', {
14
+ id: text('id').primaryKey(),
15
+ expiresAt: timestamp('expires_at').notNull(),
16
+ token: text('token').notNull().unique(),
17
+ createdAt: timestamp('created_at').notNull(),
18
+ updatedAt: timestamp('updated_at').notNull(),
19
+ ipAddress: text('ip_address'),
20
+ userAgent: text('user_agent'),
21
+ userId: text('user_id')
22
+ .notNull()
23
+ .references(() => user.id, { onDelete: 'cascade' }),
24
+ })
25
+
26
+ export const account = pgTable('account', {
27
+ id: text('id').primaryKey(),
28
+ accountId: text('account_id').notNull(),
29
+ providerId: text('provider_id').notNull(),
30
+ userId: text('user_id')
31
+ .notNull()
32
+ .references(() => user.id, { onDelete: 'cascade' }),
33
+ accessToken: text('access_token'),
34
+ refreshToken: text('refresh_token'),
35
+ idToken: text('id_token'),
36
+ accessTokenExpiresAt: timestamp('access_token_expires_at'),
37
+ refreshTokenExpiresAt: timestamp('refresh_token_expires_at'),
38
+ scope: text('scope'),
39
+ password: text('password'),
40
+ createdAt: timestamp('created_at').notNull(),
41
+ updatedAt: timestamp('updated_at').notNull(),
42
+ })
43
+
44
+ export const verification = pgTable('verification', {
45
+ id: text('id').primaryKey(),
46
+ identifier: text('identifier').notNull(),
47
+ value: text('value').notNull(),
48
+ expiresAt: timestamp('expires_at').notNull(),
49
+ createdAt: timestamp('created_at'),
50
+ updatedAt: timestamp('updated_at'),
51
+ })
@@ -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,210 @@
1
+ # AGENTS.md - Items Module
2
+
3
+ > **SDK Documentation**: For detailed module patterns, see the root [AGENTS.md](../../AGENTS.md) SDK Module System section
4
+ >
5
+ > - Server modules: `defineKuckitModule()` from `@kuckit/sdk`
6
+ > - Client modules: `defineKuckitClientModule()` from `@kuckit/sdk-react`
7
+
8
+ ## Purpose
9
+
10
+ **Reference implementation** of a Kuckit module demonstrating the full pattern.
11
+
12
+ Use this module as a template when creating new modules.
13
+
14
+ ## Structure
15
+
16
+ ```
17
+ src/
18
+ ├── domain/
19
+ │ └── item.entity.ts # Entity schema (Zod)
20
+ ├── ports/
21
+ │ └── item.repository.ts # Repository interface
22
+ ├── adapters/
23
+ │ └── item.drizzle.ts # Drizzle implementation
24
+ ├── usecases/
25
+ │ ├── create-item.ts # Create item use case
26
+ │ ├── get-item.ts # Get single item
27
+ │ ├── list-items.ts # List all items
28
+ │ └── delete-item.ts # Delete item
29
+ ├── api/
30
+ │ └── items.router.ts # oRPC router
31
+ ├── ui/
32
+ │ └── ItemsPage.tsx # React component
33
+ ├── module.ts # Server module definition
34
+ ├── client-module.ts # Client module definition
35
+ └── index.ts # Public exports
36
+ ```
37
+
38
+ ## Key Patterns
39
+
40
+ ### Domain Layer
41
+
42
+ Pure Zod schemas, no dependencies:
43
+
44
+ ```typescript
45
+ export const ItemSchema = z.object({
46
+ id: z.string().uuid(),
47
+ name: z.string().min(1),
48
+ })
49
+ ```
50
+
51
+ ### Ports Layer
52
+
53
+ Interfaces only, depend on domain:
54
+
55
+ ```typescript
56
+ export interface ItemRepository {
57
+ findAll(): Promise<Item[]>
58
+ findById(id: string): Promise<Item | null>
59
+ create(item: CreateItem): Promise<Item>
60
+ delete(id: string): Promise<void>
61
+ }
62
+ ```
63
+
64
+ ### Adapters Layer
65
+
66
+ Implement ports using Drizzle:
67
+
68
+ ```typescript
69
+ export const makeItemRepository = (db: Database): ItemRepository => ({
70
+ findAll: () => db.select().from(itemsTable),
71
+ // ...
72
+ })
73
+ ```
74
+
75
+ ### Use Cases
76
+
77
+ Business logic, depend on ports:
78
+
79
+ ```typescript
80
+ export const createItem =
81
+ (repo: ItemRepository) =>
82
+ async (input: CreateItem): Promise<Item> => {
83
+ return repo.create(input)
84
+ }
85
+ ```
86
+
87
+ ### Module Definition
88
+
89
+ Lifecycle hooks for registration:
90
+
91
+ ```typescript
92
+ export const kuckitModule = defineKuckitModule({
93
+ id: 'items',
94
+ register(ctx) {
95
+ /* DI registration */
96
+ },
97
+ registerApi(ctx) {
98
+ /* API routes */
99
+ },
100
+ onBootstrap(ctx) {
101
+ /* Startup logic */
102
+ },
103
+ })
104
+ ```
105
+
106
+ ## Creating a New Module
107
+
108
+ 1. Copy this entire `items-module` folder
109
+ 2. Rename to `your-module`
110
+ 3. Update `package.json` name
111
+ 4. Replace domain entities
112
+ 5. Update ports and adapters
113
+ 6. Implement use cases
114
+ 7. Create API router
115
+ 8. Register in server's `config/modules.ts`
116
+
117
+ ## Registration in Apps
118
+
119
+ ### Server Registration
120
+
121
+ ```typescript
122
+ // apps/server/src/config/modules.ts
123
+ import { kuckitModule as itemsModule } from '@__APP_NAME_KEBAB__/items-module'
124
+
125
+ export const modules = [itemsModule]
126
+ ```
127
+
128
+ ### Client Registration
129
+
130
+ ```typescript
131
+ // apps/web/src/modules.client.ts
132
+ import { kuckitClientModule as itemsClient } from '@__APP_NAME_KEBAB__/items-module/client'
133
+
134
+ export const clientModules = [{ module: itemsClient }]
135
+ ```
136
+
137
+ ## Client Module Pattern
138
+
139
+ The client module registers routes, navigation items, and slots:
140
+
141
+ ```typescript
142
+ // client-module.ts
143
+ import { defineKuckitClientModule } from '@kuckit/sdk-react'
144
+ import { ItemsPage } from './ui/ItemsPage'
145
+
146
+ export const kuckitClientModule = defineKuckitClientModule({
147
+ id: 'items',
148
+ displayName: 'Items',
149
+
150
+ routes: [
151
+ {
152
+ id: 'items-page',
153
+ path: '/items',
154
+ component: ItemsPage,
155
+ meta: { requiresAuth: true },
156
+ },
157
+ ],
158
+
159
+ navItems: [
160
+ {
161
+ id: 'items-nav',
162
+ label: 'Items',
163
+ href: '/items',
164
+ order: 20,
165
+ },
166
+ ],
167
+ })
168
+ ```
169
+
170
+ ## Using useRpc in Components
171
+
172
+ Module components access the API via the `useRpc` hook:
173
+
174
+ ```typescript
175
+ // ui/ItemsPage.tsx
176
+ import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query'
177
+ import { useRpc } from '@kuckit/sdk-react'
178
+
179
+ interface ItemsRpc {
180
+ items: {
181
+ list: (input: Record<string, never>) => Promise<Item[]>
182
+ create: (input: { name: string; description?: string }) => Promise<Item>
183
+ delete: (input: { id: string }) => Promise<void>
184
+ }
185
+ }
186
+
187
+ export function ItemsPage() {
188
+ const rpc = useRpc<ItemsRpc>()
189
+ const queryClient = useQueryClient()
190
+
191
+ const { data: items = [], isLoading } = useQuery({
192
+ queryKey: ['items'],
193
+ queryFn: () => rpc.items.list({}),
194
+ })
195
+
196
+ const createMutation = useMutation({
197
+ mutationFn: (data: { name: string }) => rpc.items.create(data),
198
+ onSuccess: () => queryClient.invalidateQueries({ queryKey: ['items'] }),
199
+ })
200
+
201
+ const deleteMutation = useMutation({
202
+ mutationFn: (id: string) => rpc.items.delete({ id }),
203
+ onSuccess: () => queryClient.invalidateQueries({ queryKey: ['items'] }),
204
+ })
205
+
206
+ // ... render items list with create/delete handlers
207
+ }
208
+ ```
209
+
210
+ **Important**: Never use `import.meta.env` directly in module components - use `useRpc()` instead. Module packages are bundled separately and don't have access to the host app's environment variables.
@@ -0,0 +1,32 @@
1
+ {
2
+ "name": "@__APP_NAME_KEBAB__/items-module",
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
+ "./client": {
14
+ "types": "./src/client-module.ts",
15
+ "default": "./src/client-module.ts"
16
+ }
17
+ },
18
+ "peerDependencies": {
19
+ "typescript": "^5"
20
+ },
21
+ "dependencies": {
22
+ "@kuckit/sdk": "^1.0.0",
23
+ "@kuckit/sdk-react": "^1.0.0",
24
+ "@orpc/server": "^1.10.0",
25
+ "@orpc/zod": "^1.10.0",
26
+ "drizzle-orm": "^0.44.2",
27
+ "zod": "^4.1.11",
28
+ "react": "^19.1.0",
29
+ "@__APP_NAME_KEBAB__/api": "workspace:*",
30
+ "@__APP_NAME_KEBAB__/db": "workspace:*"
31
+ }
32
+ }
@@ -0,0 +1,66 @@
1
+ import { pgTable, text, timestamp } from 'drizzle-orm/pg-core'
2
+ import { eq } from 'drizzle-orm'
3
+ import type { ItemRepository } from '../ports/item.repository'
4
+ import type { Item, CreateItemInput, UpdateItemInput } from '../domain/item.entity'
5
+
6
+ /**
7
+ * Items table schema for Drizzle ORM
8
+ */
9
+ export const itemsTable = pgTable('items', {
10
+ id: text('id').primaryKey(),
11
+ name: text('name').notNull(),
12
+ description: text('description'),
13
+ createdAt: timestamp('created_at').notNull().defaultNow(),
14
+ updatedAt: timestamp('updated_at').notNull().defaultNow(),
15
+ userId: text('user_id').notNull(),
16
+ })
17
+
18
+ /**
19
+ * Create a Drizzle-based item repository
20
+ */
21
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
22
+ export function makeItemRepository(db: any): ItemRepository {
23
+ return {
24
+ async findById(id: string): Promise<Item | null> {
25
+ const results = await db.select().from(itemsTable).where(eq(itemsTable.id, id))
26
+ return results[0] ?? null
27
+ },
28
+
29
+ async findByUserId(userId: string): Promise<Item[]> {
30
+ return db.select().from(itemsTable).where(eq(itemsTable.userId, userId))
31
+ },
32
+
33
+ async create(input: CreateItemInput & { id: string; userId: string }): Promise<Item> {
34
+ const now = new Date()
35
+ const item = {
36
+ id: input.id,
37
+ name: input.name,
38
+ description: input.description ?? null,
39
+ createdAt: now,
40
+ updatedAt: now,
41
+ userId: input.userId,
42
+ }
43
+ await db.insert(itemsTable).values(item)
44
+ return item as Item
45
+ },
46
+
47
+ async update(input: UpdateItemInput): Promise<Item | null> {
48
+ const existing = await this.findById(input.id)
49
+ if (!existing) return null
50
+
51
+ const updated = {
52
+ ...existing,
53
+ ...(input.name !== undefined && { name: input.name }),
54
+ ...(input.description !== undefined && { description: input.description }),
55
+ updatedAt: new Date(),
56
+ }
57
+ await db.update(itemsTable).set(updated).where(eq(itemsTable.id, input.id))
58
+ return updated
59
+ },
60
+
61
+ async delete(id: string): Promise<boolean> {
62
+ const result = await db.delete(itemsTable).where(eq(itemsTable.id, id))
63
+ return result.rowCount > 0
64
+ },
65
+ }
66
+ }
@@ -0,0 +1,47 @@
1
+ import { z } from 'zod'
2
+ import { protectedProcedure } from '@__APP_NAME_KEBAB__/api'
3
+ import { CreateItemInputSchema } from '../domain/item.entity'
4
+ import type { ItemRepository } from '../ports/item.repository'
5
+
6
+ /**
7
+ * Items oRPC router
8
+ * Provides CRUD operations for items
9
+ */
10
+ export const itemsRouter = {
11
+ list: protectedProcedure.input(z.object({})).handler(async ({ context }) => {
12
+ const userId = context.session?.user?.id
13
+ if (!userId) throw new Error('User not authenticated')
14
+
15
+ // In a real app, you'd resolve this from the DI container
16
+ const items = await context.di.resolve<ItemRepository>('itemRepository').findByUserId(userId)
17
+ return items
18
+ }),
19
+
20
+ get: protectedProcedure
21
+ .input(z.object({ id: z.string() }))
22
+ .handler(async ({ input, context }) => {
23
+ const item = await context.di.resolve<ItemRepository>('itemRepository').findById(input.id)
24
+ return item
25
+ }),
26
+
27
+ create: protectedProcedure.input(CreateItemInputSchema).handler(async ({ input, context }) => {
28
+ const userId = context.session?.user?.id
29
+ if (!userId) throw new Error('User not authenticated')
30
+
31
+ const id = crypto.randomUUID()
32
+ const item = await context.di.resolve<ItemRepository>('itemRepository').create({
33
+ id,
34
+ name: input.name,
35
+ description: input.description,
36
+ userId,
37
+ })
38
+ return item
39
+ }),
40
+
41
+ delete: protectedProcedure
42
+ .input(z.object({ id: z.string() }))
43
+ .handler(async ({ input, context }) => {
44
+ const success = await context.di.resolve<ItemRepository>('itemRepository').delete(input.id)
45
+ return { success }
46
+ }),
47
+ }
@@ -0,0 +1,39 @@
1
+ import { defineKuckitClientModule, type KuckitClientModuleContext } from '@kuckit/sdk-react'
2
+ import { ItemsPage } from './ui/ItemsPage'
3
+
4
+ /**
5
+ * Items client module
6
+ * Registers routes and components for the web app
7
+ */
8
+ export const kuckitClientModule = defineKuckitClientModule({
9
+ id: 'items',
10
+ displayName: 'Items',
11
+ version: '0.1.0',
12
+
13
+ register(ctx: KuckitClientModuleContext) {
14
+ // Register the items page component
15
+ ctx.registerComponent('ItemsPage', ItemsPage)
16
+
17
+ // Add route for items page
18
+ ctx.addRoute({
19
+ id: 'items',
20
+ path: '/items',
21
+ component: ItemsPage,
22
+ meta: {
23
+ title: 'Items',
24
+ requiresAuth: true,
25
+ },
26
+ })
27
+
28
+ // Add navigation item
29
+ ctx.addNavItem({
30
+ id: 'items-nav',
31
+ label: 'Items',
32
+ path: '/items',
33
+ icon: 'list',
34
+ order: 10,
35
+ })
36
+ },
37
+ })
38
+
39
+ export { ItemsPage } from './ui/ItemsPage'
@@ -0,0 +1,36 @@
1
+ import { z } from 'zod'
2
+
3
+ /**
4
+ * Item entity schema
5
+ */
6
+ export const ItemSchema = z.object({
7
+ id: z.string(),
8
+ name: z.string().min(1, 'Name is required'),
9
+ description: z.string().optional(),
10
+ createdAt: z.date(),
11
+ updatedAt: z.date(),
12
+ userId: z.string(),
13
+ })
14
+
15
+ export type Item = z.infer<typeof ItemSchema>
16
+
17
+ /**
18
+ * Create item input schema
19
+ */
20
+ export const CreateItemInputSchema = z.object({
21
+ name: z.string().min(1, 'Name is required'),
22
+ description: z.string().optional(),
23
+ })
24
+
25
+ export type CreateItemInput = z.infer<typeof CreateItemInputSchema>
26
+
27
+ /**
28
+ * Update item input schema
29
+ */
30
+ export const UpdateItemInputSchema = z.object({
31
+ id: z.string(),
32
+ name: z.string().min(1, 'Name is required').optional(),
33
+ description: z.string().optional(),
34
+ })
35
+
36
+ export type UpdateItemInput = z.infer<typeof UpdateItemInputSchema>
@@ -0,0 +1,15 @@
1
+ // Server module exports
2
+ export { kuckitModule } from './module'
3
+ export type { ItemsModuleConfig } from './module'
4
+
5
+ // Domain exports
6
+ export * from './domain/item.entity'
7
+
8
+ // Port exports
9
+ export type { ItemRepository } from './ports/item.repository'
10
+
11
+ // Use case exports
12
+ export { makeListItems } from './usecases/list-items'
13
+ export { makeCreateItem } from './usecases/create-item'
14
+ export { makeGetItem } from './usecases/get-item'
15
+ export { makeDeleteItem } from './usecases/delete-item'
@@ -0,0 +1,53 @@
1
+ import { defineKuckitModule, asFunction, type KuckitModuleContext } from '@kuckit/sdk'
2
+ import { itemsTable, makeItemRepository } from './adapters/item.drizzle'
3
+ import { itemsRouter } from './api/items.router'
4
+
5
+ export type ItemsModuleConfig = Record<string, never>
6
+
7
+ /**
8
+ * Items module - example Kuckit module demonstrating the full pattern
9
+ *
10
+ * This module shows:
11
+ * - Domain entity with Zod validation
12
+ * - Repository port/adapter pattern
13
+ * - Use cases for business logic
14
+ * - oRPC router for API endpoints
15
+ */
16
+ export const kuckitModule = defineKuckitModule<ItemsModuleConfig>({
17
+ id: 'items',
18
+ displayName: 'Items',
19
+ description: 'Example items module for CRUD operations',
20
+ version: '0.1.0',
21
+
22
+ async register(ctx: KuckitModuleContext<ItemsModuleConfig>) {
23
+ const { container } = ctx
24
+
25
+ // Register schema for migrations
26
+ ctx.registerSchema('items', itemsTable)
27
+
28
+ // Register repository
29
+ container.register({
30
+ itemRepository: asFunction(({ db }) => makeItemRepository(db)).scoped(),
31
+ })
32
+ },
33
+
34
+ registerApi(ctx) {
35
+ ctx.addApiRegistration({
36
+ type: 'rpc-router',
37
+ name: 'items',
38
+ router: itemsRouter,
39
+ })
40
+ },
41
+
42
+ async onBootstrap(ctx: KuckitModuleContext<ItemsModuleConfig>) {
43
+ const { container } = ctx
44
+ const logger = container.resolve('logger')
45
+ logger.info('Items module initialized')
46
+ },
47
+
48
+ async onShutdown(ctx: KuckitModuleContext<ItemsModuleConfig>) {
49
+ const { container } = ctx
50
+ const logger = container.resolve('logger')
51
+ logger.info('Items module shutting down')
52
+ },
53
+ })
@@ -0,0 +1,13 @@
1
+ import type { Item, CreateItemInput, UpdateItemInput } from '../domain/item.entity'
2
+
3
+ /**
4
+ * Item repository port interface
5
+ * Defines the contract for item persistence operations
6
+ */
7
+ export interface ItemRepository {
8
+ findById(id: string): Promise<Item | null>
9
+ findByUserId(userId: string): Promise<Item[]>
10
+ create(input: CreateItemInput & { id: string; userId: string }): Promise<Item>
11
+ update(input: UpdateItemInput): Promise<Item | null>
12
+ delete(id: string): Promise<boolean>
13
+ }