create-kuckit-app 2.0.0 → 2.0.2
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/package.json +1 -1
- package/templates/base/AGENTS.md +2 -2
- package/templates/base/apps/web/AGENTS.md +1 -1
- package/templates/base/apps/web/src/index.css +1 -0
- package/templates/base/apps/web/src/kuckit-modules.css +13 -0
- package/templates/base/apps/web/src/main.tsx +1 -1
- package/templates/base/kuckit.config.ts +1 -1
- package/templates/base/packages/items-module/AGENTS.md +79 -30
- package/templates/base/packages/items-module/README.md +132 -0
- package/templates/base/packages/items-module/package.json +12 -10
- package/templates/base/packages/items-module/src/server/api/items.router.ts +74 -0
- package/templates/base/packages/items-module/src/server/config.ts +27 -0
- package/templates/base/packages/items-module/src/{index.ts → server/index.ts} +1 -1
- package/templates/base/packages/items-module/src/{module.ts → server/module.ts} +22 -2
- package/templates/base/packages/items-module/src/server/usecases/update-item.ts +28 -0
- package/templates/base/packages/items-module/src/api/items.router.ts +0 -47
- package/templates/base/packages/items-module/src/ui/index.ts +0 -1
- /package/templates/base/packages/items-module/src/{client-module.ts → client/index.ts} +0 -0
- /package/templates/base/packages/items-module/src/{ui → client/ui}/ItemsPage.tsx +0 -0
- /package/templates/base/packages/items-module/src/{adapters → server/adapters}/item.drizzle.ts +0 -0
- /package/templates/base/packages/items-module/src/{domain → server/domain}/item.entity.ts +0 -0
- /package/templates/base/packages/items-module/src/{ports → server/ports}/item.repository.ts +0 -0
- /package/templates/base/packages/items-module/src/{usecases → server/usecases}/create-item.ts +0 -0
- /package/templates/base/packages/items-module/src/{usecases → server/usecases}/delete-item.ts +0 -0
- /package/templates/base/packages/items-module/src/{usecases → server/usecases}/get-item.ts +0 -0
- /package/templates/base/packages/items-module/src/{usecases → server/usecases}/list-items.ts +0 -0
package/package.json
CHANGED
package/templates/base/AGENTS.md
CHANGED
|
@@ -83,7 +83,7 @@ import { createAuthClient } from 'better-auth/react'
|
|
|
83
83
|
import config from '../kuckit.config'
|
|
84
84
|
|
|
85
85
|
const authClient = createAuthClient({
|
|
86
|
-
baseURL: import.meta.env.
|
|
86
|
+
baseURL: import.meta.env.VITE_SERVER_URL,
|
|
87
87
|
})
|
|
88
88
|
|
|
89
89
|
const KuckitProvider = createKuckitWebProvider({
|
|
@@ -452,7 +452,7 @@ See `.env.example` for required variables:
|
|
|
452
452
|
- `BETTER_AUTH_SECRET` - Auth session secret
|
|
453
453
|
- `BETTER_AUTH_URL` - Auth callback URL
|
|
454
454
|
- `PORT` - Server port (default: 3000)
|
|
455
|
-
- `
|
|
455
|
+
- `VITE_SERVER_URL` - API URL for frontend
|
|
456
456
|
|
|
457
457
|
## Troubleshooting
|
|
458
458
|
|
|
@@ -29,7 +29,7 @@ import { getClientModuleSpecs } from './modules.client'
|
|
|
29
29
|
import './index.css'
|
|
30
30
|
|
|
31
31
|
const KuckitApp = createKuckitWebProvider({
|
|
32
|
-
serverUrl: import.meta.env.
|
|
32
|
+
serverUrl: import.meta.env.VITE_SERVER_URL || 'http://localhost:3000',
|
|
33
33
|
modules: getClientModuleSpecs(),
|
|
34
34
|
env: import.meta.env.MODE,
|
|
35
35
|
routeTree,
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Kuckit Module Styles Aggregator
|
|
3
|
+
*
|
|
4
|
+
* This file is managed by the kuckit CLI. Do not edit the section between
|
|
5
|
+
* the markers manually - it will be overwritten when modules are added/removed.
|
|
6
|
+
*
|
|
7
|
+
* Module styles are automatically injected here when you run:
|
|
8
|
+
* bunx kuckit add <module-name>
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
/* KUCKIT_MODULE_STYLES_START */
|
|
12
|
+
/* Module CSS imports will be injected here by the CLI */
|
|
13
|
+
/* KUCKIT_MODULE_STYLES_END */
|
|
@@ -6,7 +6,7 @@ import { getClientModuleSpecs } from './modules.client'
|
|
|
6
6
|
import './index.css'
|
|
7
7
|
|
|
8
8
|
const KuckitApp = createKuckitWebProvider({
|
|
9
|
-
serverUrl: import.meta.env.
|
|
9
|
+
serverUrl: import.meta.env.VITE_SERVER_URL || 'http://localhost:3000',
|
|
10
10
|
modules: getClientModuleSpecs(),
|
|
11
11
|
env: import.meta.env.MODE,
|
|
12
12
|
routeTree,
|
|
@@ -11,7 +11,7 @@ import { defineConfig } from '@kuckit/sdk/config'
|
|
|
11
11
|
export default defineConfig({
|
|
12
12
|
modules: [
|
|
13
13
|
// KUCKIT_MODULES_START
|
|
14
|
-
{ package: '@__APP_NAME_KEBAB__/items-module'
|
|
14
|
+
{ package: '@__APP_NAME_KEBAB__/items-module' },
|
|
15
15
|
// KUCKIT_MODULES_END
|
|
16
16
|
],
|
|
17
17
|
|
|
@@ -15,26 +15,74 @@ Use this module as a template when creating new modules.
|
|
|
15
15
|
|
|
16
16
|
```
|
|
17
17
|
src/
|
|
18
|
-
├──
|
|
19
|
-
│
|
|
20
|
-
|
|
21
|
-
│
|
|
22
|
-
|
|
23
|
-
│
|
|
24
|
-
|
|
25
|
-
│ ├──
|
|
26
|
-
│ ├──
|
|
27
|
-
│ ├──
|
|
28
|
-
│
|
|
29
|
-
├──
|
|
30
|
-
│ └──
|
|
31
|
-
├──
|
|
32
|
-
│ └──
|
|
33
|
-
├──
|
|
34
|
-
├──
|
|
35
|
-
└── index.ts #
|
|
18
|
+
├── server/
|
|
19
|
+
│ ├── domain/
|
|
20
|
+
│ │ └── item.entity.ts # Entity schema (Zod)
|
|
21
|
+
│ ├── ports/
|
|
22
|
+
│ │ └── item.repository.ts # Repository interface
|
|
23
|
+
│ ├── adapters/
|
|
24
|
+
│ │ └── item.drizzle.ts # Drizzle implementation
|
|
25
|
+
│ ├── usecases/
|
|
26
|
+
│ │ ├── create-item.ts # Create item use case
|
|
27
|
+
│ │ ├── get-item.ts # Get single item
|
|
28
|
+
│ │ ├── list-items.ts # List all items
|
|
29
|
+
│ │ ├── update-item.ts # Update item
|
|
30
|
+
│ │ └── delete-item.ts # Delete item
|
|
31
|
+
│ ├── api/
|
|
32
|
+
│ │ └── items.router.ts # oRPC router
|
|
33
|
+
│ ├── config.ts # Module config with Zod schema
|
|
34
|
+
│ ├── module.ts # Server module definition
|
|
35
|
+
│ └── index.ts # Server exports
|
|
36
|
+
└── client/
|
|
37
|
+
├── ui/
|
|
38
|
+
│ └── ItemsPage.tsx # React component
|
|
39
|
+
└── index.ts # Client module + exports
|
|
36
40
|
```
|
|
37
41
|
|
|
42
|
+
## Configuration Options
|
|
43
|
+
|
|
44
|
+
| Option | Type | Default | Description |
|
|
45
|
+
| ------------------ | --------- | ------- | ------------------------------------------------ |
|
|
46
|
+
| `maxItemsPerPage` | `number` | `100` | Maximum items returned per list request (1-1000) |
|
|
47
|
+
| `enableSoftDelete` | `boolean` | `false` | Mark items as deleted instead of hard delete |
|
|
48
|
+
|
|
49
|
+
Configure via `kuckit.config.ts`:
|
|
50
|
+
|
|
51
|
+
```typescript
|
|
52
|
+
modules: [
|
|
53
|
+
{
|
|
54
|
+
spec: itemsModule,
|
|
55
|
+
config: {
|
|
56
|
+
maxItemsPerPage: 50,
|
|
57
|
+
enableSoftDelete: true,
|
|
58
|
+
},
|
|
59
|
+
},
|
|
60
|
+
]
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
## DI Registrations
|
|
64
|
+
|
|
65
|
+
| Token | Type | Lifetime | Description |
|
|
66
|
+
| ---------------- | ------------------- | -------- | --------------------------- |
|
|
67
|
+
| `itemRepository` | `ItemRepository` | Scoped | Data access for items table |
|
|
68
|
+
| `listItems` | `ListItemsUseCase` | Scoped | List items by user |
|
|
69
|
+
| `createItem` | `CreateItemUseCase` | Scoped | Create new item |
|
|
70
|
+
| `getItem` | `GetItemUseCase` | Scoped | Get single item by ID |
|
|
71
|
+
| `updateItem` | `UpdateItemUseCase` | Scoped | Update existing item |
|
|
72
|
+
| `deleteItem` | `DeleteItemUseCase` | Scoped | Delete item by ID |
|
|
73
|
+
|
|
74
|
+
## API Endpoints
|
|
75
|
+
|
|
76
|
+
All endpoints require authentication (`protectedProcedure`).
|
|
77
|
+
|
|
78
|
+
| Endpoint | Method | Input | Output | Description |
|
|
79
|
+
| -------------- | ------ | ---------------------------------------- | ------------------- | ----------------- |
|
|
80
|
+
| `items.list` | RPC | `{}` | `Item[]` | List user's items |
|
|
81
|
+
| `items.get` | RPC | `{ id: string }` | `Item \| null` | Get item by ID |
|
|
82
|
+
| `items.create` | RPC | `{ name: string, description?: string }` | `Item` | Create new item |
|
|
83
|
+
| `items.update` | RPC | `{ id, name?, description? }` | `Item` | Update item |
|
|
84
|
+
| `items.delete` | RPC | `{ id: string }` | `{ success: bool }` | Delete item |
|
|
85
|
+
|
|
38
86
|
## Key Patterns
|
|
39
87
|
|
|
40
88
|
### Domain Layer
|
|
@@ -139,7 +187,7 @@ export const clientModules = [{ module: itemsClient }]
|
|
|
139
187
|
The client module registers routes, navigation items, and slots:
|
|
140
188
|
|
|
141
189
|
```typescript
|
|
142
|
-
// client
|
|
190
|
+
// src/client/index.ts
|
|
143
191
|
import { defineKuckitClientModule } from '@kuckit/sdk-react'
|
|
144
192
|
import { ItemsPage } from './ui/ItemsPage'
|
|
145
193
|
|
|
@@ -147,23 +195,24 @@ export const kuckitClientModule = defineKuckitClientModule({
|
|
|
147
195
|
id: 'items',
|
|
148
196
|
displayName: 'Items',
|
|
149
197
|
|
|
150
|
-
|
|
151
|
-
|
|
198
|
+
register(ctx) {
|
|
199
|
+
ctx.registerComponent('ItemsPage', ItemsPage)
|
|
200
|
+
|
|
201
|
+
ctx.addRoute({
|
|
152
202
|
id: 'items-page',
|
|
153
203
|
path: '/items',
|
|
154
204
|
component: ItemsPage,
|
|
155
205
|
meta: { requiresAuth: true },
|
|
156
|
-
}
|
|
157
|
-
],
|
|
206
|
+
})
|
|
158
207
|
|
|
159
|
-
|
|
160
|
-
{
|
|
208
|
+
ctx.addNavItem({
|
|
161
209
|
id: 'items-nav',
|
|
162
210
|
label: 'Items',
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
211
|
+
path: '/items',
|
|
212
|
+
icon: 'list',
|
|
213
|
+
order: 10,
|
|
214
|
+
})
|
|
215
|
+
},
|
|
167
216
|
})
|
|
168
217
|
```
|
|
169
218
|
|
|
@@ -172,7 +221,7 @@ export const kuckitClientModule = defineKuckitClientModule({
|
|
|
172
221
|
Module components access the API via the `useRpc` hook:
|
|
173
222
|
|
|
174
223
|
```typescript
|
|
175
|
-
// ui/ItemsPage.tsx
|
|
224
|
+
// src/client/ui/ItemsPage.tsx
|
|
176
225
|
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query'
|
|
177
226
|
import { useRpc } from '@kuckit/sdk-react'
|
|
178
227
|
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
# Items Module
|
|
2
|
+
|
|
3
|
+
> **Reference implementation** of a Kuckit module demonstrating Clean Architecture patterns.
|
|
4
|
+
|
|
5
|
+
Use this module as a template when creating new modules for your Kuckit application.
|
|
6
|
+
|
|
7
|
+
## Features
|
|
8
|
+
|
|
9
|
+
- **CRUD operations** for items with full validation
|
|
10
|
+
- **Clean Architecture** with domain, ports, adapters, use cases, and API layers
|
|
11
|
+
- **Type-safe API** using oRPC with Zod schemas
|
|
12
|
+
- **React components** with TanStack Query integration
|
|
13
|
+
- **Configurable** via `kuckit.config.ts`
|
|
14
|
+
|
|
15
|
+
## Installation
|
|
16
|
+
|
|
17
|
+
The items-module is included by default in projects scaffolded with `create-kuckit-app`.
|
|
18
|
+
|
|
19
|
+
For manual installation:
|
|
20
|
+
|
|
21
|
+
```bash
|
|
22
|
+
# Copy the module to your project
|
|
23
|
+
cp -r packages/items-module packages/your-module
|
|
24
|
+
|
|
25
|
+
# Update package.json name
|
|
26
|
+
# Edit packages/your-module/package.json
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
## Usage
|
|
30
|
+
|
|
31
|
+
### Server Registration
|
|
32
|
+
|
|
33
|
+
```typescript
|
|
34
|
+
// apps/server/src/config/modules.ts
|
|
35
|
+
import { kuckitModule as itemsModule } from '@your-app/items-module'
|
|
36
|
+
|
|
37
|
+
export const getModuleSpecs = () => [{ spec: itemsModule }]
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
### Client Registration
|
|
41
|
+
|
|
42
|
+
```typescript
|
|
43
|
+
// apps/web/src/modules.client.ts
|
|
44
|
+
import { kuckitClientModule as itemsClient } from '@your-app/items-module/client'
|
|
45
|
+
|
|
46
|
+
export const clientModules = [{ module: itemsClient }]
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
### Database Setup
|
|
50
|
+
|
|
51
|
+
Add the module's schema to your Drizzle config:
|
|
52
|
+
|
|
53
|
+
```typescript
|
|
54
|
+
// drizzle.config.ts
|
|
55
|
+
schema: [
|
|
56
|
+
'./node_modules/@kuckit/db/dist/schema/auth.js',
|
|
57
|
+
'./packages/items-module/src/server/adapters',
|
|
58
|
+
],
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
Then run migrations:
|
|
62
|
+
|
|
63
|
+
```bash
|
|
64
|
+
bun run db:push # Development
|
|
65
|
+
bun run db:migrate # Production
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
## Configuration
|
|
69
|
+
|
|
70
|
+
Configure the module in `kuckit.config.ts`:
|
|
71
|
+
|
|
72
|
+
```typescript
|
|
73
|
+
import { kuckitModule as itemsModule } from '@your-app/items-module'
|
|
74
|
+
|
|
75
|
+
export default defineKuckitConfig({
|
|
76
|
+
modules: [
|
|
77
|
+
{
|
|
78
|
+
spec: itemsModule,
|
|
79
|
+
config: {
|
|
80
|
+
maxItemsPerPage: 50, // Default: 100
|
|
81
|
+
enableSoftDelete: true, // Default: false
|
|
82
|
+
},
|
|
83
|
+
},
|
|
84
|
+
],
|
|
85
|
+
})
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
## API Reference
|
|
89
|
+
|
|
90
|
+
All endpoints require authentication.
|
|
91
|
+
|
|
92
|
+
| Endpoint | Input | Output | Description |
|
|
93
|
+
| -------------- | ---------------------------------------- | -------------- | ----------------- |
|
|
94
|
+
| `items.list` | `{}` | `Item[]` | List user's items |
|
|
95
|
+
| `items.get` | `{ id: string }` | `Item \| null` | Get item by ID |
|
|
96
|
+
| `items.create` | `{ name: string, description?: string }` | `Item` | Create new item |
|
|
97
|
+
| `items.update` | `{ id, name?, description? }` | `Item` | Update item |
|
|
98
|
+
| `items.delete` | `{ id: string }` | `{ success }` | Delete item |
|
|
99
|
+
|
|
100
|
+
## Project Structure
|
|
101
|
+
|
|
102
|
+
```
|
|
103
|
+
src/
|
|
104
|
+
├── server/ # Backend code
|
|
105
|
+
│ ├── domain/ # Entity schemas (Zod)
|
|
106
|
+
│ ├── ports/ # Repository interfaces
|
|
107
|
+
│ ├── adapters/ # Drizzle implementations
|
|
108
|
+
│ ├── usecases/ # Business logic
|
|
109
|
+
│ ├── api/ # oRPC router
|
|
110
|
+
│ ├── config.ts # Module configuration
|
|
111
|
+
│ └── module.ts # Server module definition
|
|
112
|
+
└── client/ # Frontend code
|
|
113
|
+
├── ui/ # React components
|
|
114
|
+
└── index.ts # Client module definition
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
## Creating a New Module
|
|
118
|
+
|
|
119
|
+
1. Copy this module: `cp -r packages/items-module packages/your-module`
|
|
120
|
+
2. Update `package.json` name to `@your-app/your-module`
|
|
121
|
+
3. Replace domain entities in `src/server/domain/`
|
|
122
|
+
4. Update ports and adapters
|
|
123
|
+
5. Implement use cases
|
|
124
|
+
6. Create API router
|
|
125
|
+
7. Register in both server and client configs
|
|
126
|
+
8. Add schema path to `drizzle.config.ts`
|
|
127
|
+
9. Run `bun run db:push`
|
|
128
|
+
|
|
129
|
+
## Documentation
|
|
130
|
+
|
|
131
|
+
- [AGENTS.md](./AGENTS.md) - AI assistant guidance and detailed patterns
|
|
132
|
+
- [Kuckit SDK](https://github.com/draphonix/kuckit) - Framework documentation
|
|
@@ -3,22 +3,24 @@
|
|
|
3
3
|
"version": "0.0.1",
|
|
4
4
|
"private": true,
|
|
5
5
|
"type": "module",
|
|
6
|
-
"main": "src/
|
|
7
|
-
"types": "src/
|
|
6
|
+
"main": "src/server/module.ts",
|
|
7
|
+
"types": "src/server/module.ts",
|
|
8
8
|
"exports": {
|
|
9
9
|
".": {
|
|
10
|
-
"types": "./src/index.ts",
|
|
11
|
-
"default": "./src/index.ts"
|
|
10
|
+
"types": "./src/server/index.ts",
|
|
11
|
+
"default": "./src/server/index.ts"
|
|
12
12
|
},
|
|
13
13
|
"./client": {
|
|
14
|
-
"types": "./src/client
|
|
15
|
-
"default": "./src/client
|
|
16
|
-
},
|
|
17
|
-
"./ui": {
|
|
18
|
-
"types": "./src/ui/index.ts",
|
|
19
|
-
"default": "./src/ui/index.ts"
|
|
14
|
+
"types": "./src/client/index.ts",
|
|
15
|
+
"default": "./src/client/index.ts"
|
|
20
16
|
}
|
|
21
17
|
},
|
|
18
|
+
"kuckit": {
|
|
19
|
+
"id": "items",
|
|
20
|
+
"server": ".",
|
|
21
|
+
"client": "./client",
|
|
22
|
+
"schemaDir": "src/server/adapters"
|
|
23
|
+
},
|
|
22
24
|
"peerDependencies": {
|
|
23
25
|
"typescript": "^5"
|
|
24
26
|
},
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import { z } from 'zod'
|
|
2
|
+
import { protectedProcedure } from '@kuckit/api'
|
|
3
|
+
import { CreateItemInputSchema, UpdateItemInputSchema } from '../domain/item.entity'
|
|
4
|
+
import type { Item } from '../domain/item.entity'
|
|
5
|
+
import type { ListItemsInput, ListItemsOutput } from '../usecases/list-items'
|
|
6
|
+
import type { CreateItemUseCaseInput } from '../usecases/create-item'
|
|
7
|
+
import type { GetItemInput } from '../usecases/get-item'
|
|
8
|
+
import type { DeleteItemInput } from '../usecases/delete-item'
|
|
9
|
+
import type { UpdateItemUseCaseInput } from '../usecases/update-item'
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Items cradle interface for DI resolution
|
|
13
|
+
*/
|
|
14
|
+
interface ItemsCradle {
|
|
15
|
+
listItems: (input: ListItemsInput) => Promise<ListItemsOutput>
|
|
16
|
+
createItem: (input: CreateItemUseCaseInput) => Promise<Item>
|
|
17
|
+
getItem: (input: GetItemInput) => Promise<Item | null>
|
|
18
|
+
deleteItem: (input: DeleteItemInput) => Promise<boolean>
|
|
19
|
+
updateItem: (input: UpdateItemUseCaseInput) => Promise<Item>
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Items oRPC router
|
|
24
|
+
* Provides CRUD operations for items
|
|
25
|
+
*/
|
|
26
|
+
export const itemsRouter = {
|
|
27
|
+
list: protectedProcedure.input(z.object({})).handler(async ({ context }) => {
|
|
28
|
+
const userId = context.session?.user?.id
|
|
29
|
+
if (!userId) throw new Error('User not authenticated')
|
|
30
|
+
|
|
31
|
+
const { listItems } = context.di.cradle as ItemsCradle
|
|
32
|
+
return listItems({ userId })
|
|
33
|
+
}),
|
|
34
|
+
|
|
35
|
+
get: protectedProcedure
|
|
36
|
+
.input(z.object({ id: z.string() }))
|
|
37
|
+
.handler(async ({ input, context }) => {
|
|
38
|
+
const { getItem } = context.di.cradle as ItemsCradle
|
|
39
|
+
return getItem({ id: input.id })
|
|
40
|
+
}),
|
|
41
|
+
|
|
42
|
+
create: protectedProcedure.input(CreateItemInputSchema).handler(async ({ input, context }) => {
|
|
43
|
+
const userId = context.session?.user?.id
|
|
44
|
+
if (!userId) throw new Error('User not authenticated')
|
|
45
|
+
|
|
46
|
+
const { createItem } = context.di.cradle as ItemsCradle
|
|
47
|
+
return createItem({
|
|
48
|
+
name: input.name,
|
|
49
|
+
description: input.description,
|
|
50
|
+
userId,
|
|
51
|
+
})
|
|
52
|
+
}),
|
|
53
|
+
|
|
54
|
+
delete: protectedProcedure
|
|
55
|
+
.input(z.object({ id: z.string() }))
|
|
56
|
+
.handler(async ({ input, context }) => {
|
|
57
|
+
const { deleteItem } = context.di.cradle as ItemsCradle
|
|
58
|
+
const success = await deleteItem({ id: input.id })
|
|
59
|
+
return { success }
|
|
60
|
+
}),
|
|
61
|
+
|
|
62
|
+
update: protectedProcedure.input(UpdateItemInputSchema).handler(async ({ input, context }) => {
|
|
63
|
+
const userId = context.session?.user?.id
|
|
64
|
+
if (!userId) throw new Error('User not authenticated')
|
|
65
|
+
|
|
66
|
+
const { updateItem } = context.di.cradle as ItemsCradle
|
|
67
|
+
return updateItem({
|
|
68
|
+
id: input.id,
|
|
69
|
+
name: input.name,
|
|
70
|
+
description: input.description,
|
|
71
|
+
userId,
|
|
72
|
+
})
|
|
73
|
+
}),
|
|
74
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { z } from 'zod'
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Items module configuration schema
|
|
5
|
+
*
|
|
6
|
+
* This is an example module with minimal configuration.
|
|
7
|
+
* Real modules might include settings for:
|
|
8
|
+
* - Feature flags
|
|
9
|
+
* - Pagination limits
|
|
10
|
+
* - Cache TTLs
|
|
11
|
+
* - External service URLs
|
|
12
|
+
*/
|
|
13
|
+
export const itemsModuleConfigSchema = z.object({
|
|
14
|
+
/**
|
|
15
|
+
* Maximum number of items to return per list request
|
|
16
|
+
* @default 100
|
|
17
|
+
*/
|
|
18
|
+
maxItemsPerPage: z.number().min(1).max(1000).default(100),
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Enable soft delete (mark as deleted instead of hard delete)
|
|
22
|
+
* @default false
|
|
23
|
+
*/
|
|
24
|
+
enableSoftDelete: z.boolean().default(false),
|
|
25
|
+
})
|
|
26
|
+
|
|
27
|
+
export type ItemsModuleConfig = z.infer<typeof itemsModuleConfigSchema>
|
|
@@ -1,8 +1,14 @@
|
|
|
1
1
|
import { defineKuckitModule, asFunction, type KuckitModuleContext } from '@kuckit/sdk'
|
|
2
2
|
import { itemsTable, makeItemRepository } from './adapters/item.drizzle'
|
|
3
3
|
import { itemsRouter } from './api/items.router'
|
|
4
|
+
import { itemsModuleConfigSchema, type ItemsModuleConfig } from './config'
|
|
5
|
+
import { makeListItems } from './usecases/list-items'
|
|
6
|
+
import { makeCreateItem } from './usecases/create-item'
|
|
7
|
+
import { makeGetItem } from './usecases/get-item'
|
|
8
|
+
import { makeDeleteItem } from './usecases/delete-item'
|
|
9
|
+
import { makeUpdateItem } from './usecases/update-item'
|
|
4
10
|
|
|
5
|
-
export type ItemsModuleConfig
|
|
11
|
+
export type { ItemsModuleConfig }
|
|
6
12
|
|
|
7
13
|
/**
|
|
8
14
|
* Items module - example Kuckit module demonstrating the full pattern
|
|
@@ -20,15 +26,29 @@ export const kuckitModule = defineKuckitModule<ItemsModuleConfig>({
|
|
|
20
26
|
version: '0.1.0',
|
|
21
27
|
|
|
22
28
|
async register(ctx: KuckitModuleContext<ItemsModuleConfig>) {
|
|
23
|
-
const { container } = ctx
|
|
29
|
+
const { container, config } = ctx
|
|
30
|
+
const resolvedConfig = itemsModuleConfigSchema.parse(config ?? {})
|
|
24
31
|
|
|
25
32
|
// Register schema for migrations
|
|
26
33
|
ctx.registerSchema('items', itemsTable)
|
|
27
34
|
|
|
35
|
+
// Log configuration
|
|
36
|
+
const logger = container.resolve('logger')
|
|
37
|
+
logger.debug('Items module config', { config: resolvedConfig })
|
|
38
|
+
|
|
28
39
|
// Register repository
|
|
29
40
|
container.register({
|
|
30
41
|
itemRepository: asFunction(({ db }) => makeItemRepository(db)).scoped(),
|
|
31
42
|
})
|
|
43
|
+
|
|
44
|
+
// Register use cases
|
|
45
|
+
container.register({
|
|
46
|
+
listItems: asFunction(({ itemRepository }) => makeListItems({ itemRepository })).scoped(),
|
|
47
|
+
createItem: asFunction(({ itemRepository }) => makeCreateItem({ itemRepository })).scoped(),
|
|
48
|
+
getItem: asFunction(({ itemRepository }) => makeGetItem({ itemRepository })).scoped(),
|
|
49
|
+
deleteItem: asFunction(({ itemRepository }) => makeDeleteItem({ itemRepository })).scoped(),
|
|
50
|
+
updateItem: asFunction(({ itemRepository }) => makeUpdateItem({ itemRepository })).scoped(),
|
|
51
|
+
})
|
|
32
52
|
},
|
|
33
53
|
|
|
34
54
|
registerApi(ctx) {
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import type { ItemRepository } from '../ports/item.repository'
|
|
2
|
+
import type { Item, UpdateItemInput } from '../domain/item.entity'
|
|
3
|
+
|
|
4
|
+
export interface UpdateItemUseCaseInput extends UpdateItemInput {
|
|
5
|
+
userId: string
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
interface UpdateItemDeps {
|
|
9
|
+
itemRepository: ItemRepository
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Update item use case
|
|
14
|
+
* Validates ownership before allowing update
|
|
15
|
+
*/
|
|
16
|
+
export function makeUpdateItem(deps: UpdateItemDeps) {
|
|
17
|
+
return async (input: UpdateItemUseCaseInput): Promise<Item> => {
|
|
18
|
+
const existing = await deps.itemRepository.findById(input.id)
|
|
19
|
+
if (!existing || existing.userId !== input.userId) {
|
|
20
|
+
throw new Error('Item not found')
|
|
21
|
+
}
|
|
22
|
+
const updated = await deps.itemRepository.update(input)
|
|
23
|
+
if (!updated) {
|
|
24
|
+
throw new Error('Failed to update item')
|
|
25
|
+
}
|
|
26
|
+
return updated
|
|
27
|
+
}
|
|
28
|
+
}
|
|
@@ -1,47 +0,0 @@
|
|
|
1
|
-
import { z } from 'zod'
|
|
2
|
-
import { protectedProcedure } from '@kuckit/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
|
-
}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export { ItemsPage } from './ItemsPage'
|
|
File without changes
|
|
File without changes
|
/package/templates/base/packages/items-module/src/{adapters → server/adapters}/item.drizzle.ts
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
/package/templates/base/packages/items-module/src/{usecases → server/usecases}/create-item.ts
RENAMED
|
File without changes
|
/package/templates/base/packages/items-module/src/{usecases → server/usecases}/delete-item.ts
RENAMED
|
File without changes
|
|
File without changes
|
/package/templates/base/packages/items-module/src/{usecases → server/usecases}/list-items.ts
RENAMED
|
File without changes
|