@wordrhyme/auto-crud-server 0.2.0
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/LICENSE +21 -0
- package/README.md +712 -0
- package/dist/index.cjs +529 -0
- package/dist/index.d.cts +172 -0
- package/dist/index.d.ts +172 -0
- package/dist/index.js +498 -0
- package/package.json +67 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 Pixpilot
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,712 @@
|
|
|
1
|
+
# @wordrhyme/auto-crud-server
|
|
2
|
+
|
|
3
|
+
> tRPC server utilities for auto-crud - automatic CRUD routers for Drizzle ORM
|
|
4
|
+
|
|
5
|
+
为 Drizzle ORM 表自动生成 tRPC CRUD 路由,支持分页、排序、高级过滤。
|
|
6
|
+
|
|
7
|
+
[](https://www.npmjs.com/package/@wordrhyme/auto-crud-server)
|
|
8
|
+
[](https://opensource.org/licenses/MIT)
|
|
9
|
+
|
|
10
|
+
---
|
|
11
|
+
|
|
12
|
+
## ✨ 特性
|
|
13
|
+
|
|
14
|
+
- 🚀 **零代码 CRUD**: 一行代码生成完整 CRUD 路由
|
|
15
|
+
- 🔒 **类型安全**: 完整的 TypeScript 类型推断
|
|
16
|
+
- 🎯 **高级过滤**: 支持多条件、多操作符(等于、包含、范围等)
|
|
17
|
+
- 📊 **排序分页**: 内置分页和多字段排序支持
|
|
18
|
+
- 🔌 **Drizzle 集成**: 无缝集成 Drizzle ORM
|
|
19
|
+
- 🌐 **tRPC 原生**: 基于 tRPC v11 构建
|
|
20
|
+
- 📦 **零依赖**: 所有依赖都是 peer dependencies
|
|
21
|
+
|
|
22
|
+
---
|
|
23
|
+
|
|
24
|
+
## 📦 安装
|
|
25
|
+
|
|
26
|
+
```bash
|
|
27
|
+
# pnpm
|
|
28
|
+
pnpm add @wordrhyme/auto-crud-server
|
|
29
|
+
|
|
30
|
+
# npm
|
|
31
|
+
npm install @wordrhyme/auto-crud-server
|
|
32
|
+
|
|
33
|
+
# yarn
|
|
34
|
+
yarn add @wordrhyme/auto-crud-server
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
### Peer Dependencies
|
|
38
|
+
|
|
39
|
+
```json
|
|
40
|
+
{
|
|
41
|
+
"@trpc/server": "^11.0.0",
|
|
42
|
+
"drizzle-orm": "^0.40.0",
|
|
43
|
+
"zod": "^3.0.0 || ^4.0.0"
|
|
44
|
+
}
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
---
|
|
48
|
+
|
|
49
|
+
## 🚀 快速开始
|
|
50
|
+
|
|
51
|
+
### 步骤 1: 定义数据库 Schema
|
|
52
|
+
|
|
53
|
+
```typescript
|
|
54
|
+
// src/db/schema.ts
|
|
55
|
+
import { pgTable, varchar, real, boolean, timestamp } from "drizzle-orm/pg-core";
|
|
56
|
+
|
|
57
|
+
export const tasks = pgTable("tasks", {
|
|
58
|
+
id: varchar("id", { length: 30 }).primaryKey(),
|
|
59
|
+
title: varchar("title", { length: 128 }).notNull(),
|
|
60
|
+
status: varchar("status", {
|
|
61
|
+
enum: ["todo", "in-progress", "done", "canceled"],
|
|
62
|
+
}).notNull().default("todo"),
|
|
63
|
+
priority: varchar("priority", {
|
|
64
|
+
enum: ["low", "medium", "high"],
|
|
65
|
+
}).notNull().default("low"),
|
|
66
|
+
estimatedHours: real("estimated_hours").default(0),
|
|
67
|
+
createdAt: timestamp("created_at").defaultNow().notNull(),
|
|
68
|
+
updatedAt: timestamp("updated_at").defaultNow(),
|
|
69
|
+
});
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
### 步骤 2: 创建 CRUD 路由
|
|
73
|
+
|
|
74
|
+
```typescript
|
|
75
|
+
// src/server/routers/tasks.ts
|
|
76
|
+
import { createCrudRouter } from "@wordrhyme/auto-crud-server";
|
|
77
|
+
import { tasks } from "@/db/schema";
|
|
78
|
+
import { createSelectSchema } from "drizzle-zod";
|
|
79
|
+
|
|
80
|
+
// 从 Drizzle Schema 自动生成 Zod Schema
|
|
81
|
+
const selectTaskSchema = createSelectSchema(tasks);
|
|
82
|
+
|
|
83
|
+
// 创建输入 Schema
|
|
84
|
+
const insertTaskSchema = selectTaskSchema.omit({
|
|
85
|
+
id: true,
|
|
86
|
+
createdAt: true,
|
|
87
|
+
updatedAt: true,
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
const updateTaskSchema = insertTaskSchema.partial();
|
|
91
|
+
|
|
92
|
+
// 🚀 一行代码生成完整 CRUD 路由
|
|
93
|
+
export const tasksRouter = createCrudRouter({
|
|
94
|
+
table: tasks,
|
|
95
|
+
selectSchema: selectTaskSchema,
|
|
96
|
+
insertSchema: insertTaskSchema,
|
|
97
|
+
updateSchema: updateTaskSchema,
|
|
98
|
+
idField: "id", // 可选,默认为 "id"
|
|
99
|
+
});
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
### 步骤 3: 汇总路由
|
|
103
|
+
|
|
104
|
+
```typescript
|
|
105
|
+
// src/server/routers/index.ts
|
|
106
|
+
import { router } from "../trpc";
|
|
107
|
+
import { tasksRouter } from "./tasks";
|
|
108
|
+
|
|
109
|
+
export const appRouter = router({
|
|
110
|
+
tasks: tasksRouter,
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
export type AppRouter = typeof appRouter;
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
### 步骤 4: 创建 API 路由处理器
|
|
117
|
+
|
|
118
|
+
```typescript
|
|
119
|
+
// src/app/api/trpc/[trpc]/route.ts (Next.js App Router)
|
|
120
|
+
import { fetchRequestHandler } from "@trpc/server/adapters/fetch";
|
|
121
|
+
import { appRouter } from "@/server/routers";
|
|
122
|
+
import { db } from "@/db";
|
|
123
|
+
|
|
124
|
+
const handler = (req: Request) =>
|
|
125
|
+
fetchRequestHandler({
|
|
126
|
+
endpoint: "/api/trpc",
|
|
127
|
+
req,
|
|
128
|
+
router: appRouter,
|
|
129
|
+
createContext: () => ({ db }),
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
export { handler as GET, handler as POST };
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
---
|
|
136
|
+
|
|
137
|
+
## 📖 自动生成的路由
|
|
138
|
+
|
|
139
|
+
`createCrudRouter` 会自动生成以下 6 个路由:
|
|
140
|
+
|
|
141
|
+
### 1. `list` - 列表查询
|
|
142
|
+
|
|
143
|
+
**输入**:
|
|
144
|
+
```typescript
|
|
145
|
+
{
|
|
146
|
+
page: number; // 页码(从 1 开始)
|
|
147
|
+
perPage: number; // 每页条数
|
|
148
|
+
sort?: Array<{ // 排序
|
|
149
|
+
id: string; // 字段名
|
|
150
|
+
desc: boolean; // 是否降序
|
|
151
|
+
}>;
|
|
152
|
+
filters?: Array<{ // 过滤条件
|
|
153
|
+
id: string; // 字段名
|
|
154
|
+
value: any; // 值
|
|
155
|
+
operator: string; // 操作符
|
|
156
|
+
variant: string; // 筛选器类型
|
|
157
|
+
}>;
|
|
158
|
+
joinOperator?: "and" | "or"; // 多条件连接方式
|
|
159
|
+
}
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
**输出**:
|
|
163
|
+
```typescript
|
|
164
|
+
{
|
|
165
|
+
data: Task[]; // 数据列表
|
|
166
|
+
total: number; // 总条数
|
|
167
|
+
}
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
**示例**:
|
|
171
|
+
```typescript
|
|
172
|
+
const result = await trpc.tasks.list({
|
|
173
|
+
page: 1,
|
|
174
|
+
perPage: 10,
|
|
175
|
+
sort: [{ id: "createdAt", desc: true }],
|
|
176
|
+
filters: [
|
|
177
|
+
{ id: "status", value: "done", operator: "eq", variant: "select" },
|
|
178
|
+
{ id: "priority", value: "high", operator: "eq", variant: "select" },
|
|
179
|
+
],
|
|
180
|
+
joinOperator: "and",
|
|
181
|
+
});
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
### 2. `get` - 单条查询
|
|
185
|
+
|
|
186
|
+
**输入**:
|
|
187
|
+
```typescript
|
|
188
|
+
{ id: string }
|
|
189
|
+
```
|
|
190
|
+
|
|
191
|
+
**输出**:
|
|
192
|
+
```typescript
|
|
193
|
+
Task
|
|
194
|
+
```
|
|
195
|
+
|
|
196
|
+
**示例**:
|
|
197
|
+
```typescript
|
|
198
|
+
const task = await trpc.tasks.get({ id: "123" });
|
|
199
|
+
```
|
|
200
|
+
|
|
201
|
+
### 3. `create` - 创建
|
|
202
|
+
|
|
203
|
+
**输入**:
|
|
204
|
+
```typescript
|
|
205
|
+
Omit<Task, "id" | "createdAt" | "updatedAt">
|
|
206
|
+
```
|
|
207
|
+
|
|
208
|
+
**输出**:
|
|
209
|
+
```typescript
|
|
210
|
+
Task
|
|
211
|
+
```
|
|
212
|
+
|
|
213
|
+
**示例**:
|
|
214
|
+
```typescript
|
|
215
|
+
const newTask = await trpc.tasks.create({
|
|
216
|
+
title: "New Task",
|
|
217
|
+
status: "todo",
|
|
218
|
+
priority: "high",
|
|
219
|
+
});
|
|
220
|
+
```
|
|
221
|
+
|
|
222
|
+
### 4. `update` - 更新
|
|
223
|
+
|
|
224
|
+
**输入**:
|
|
225
|
+
```typescript
|
|
226
|
+
{
|
|
227
|
+
id: string;
|
|
228
|
+
data: Partial<Omit<Task, "id" | "createdAt" | "updatedAt">>;
|
|
229
|
+
}
|
|
230
|
+
```
|
|
231
|
+
|
|
232
|
+
**输出**:
|
|
233
|
+
```typescript
|
|
234
|
+
Task
|
|
235
|
+
```
|
|
236
|
+
|
|
237
|
+
**示例**:
|
|
238
|
+
```typescript
|
|
239
|
+
const updatedTask = await trpc.tasks.update({
|
|
240
|
+
id: "123",
|
|
241
|
+
data: { status: "done" },
|
|
242
|
+
});
|
|
243
|
+
```
|
|
244
|
+
|
|
245
|
+
### 5. `delete` - 删除
|
|
246
|
+
|
|
247
|
+
**输入**:
|
|
248
|
+
```typescript
|
|
249
|
+
{ id: string }
|
|
250
|
+
```
|
|
251
|
+
|
|
252
|
+
**输出**:
|
|
253
|
+
```typescript
|
|
254
|
+
void
|
|
255
|
+
```
|
|
256
|
+
|
|
257
|
+
**示例**:
|
|
258
|
+
```typescript
|
|
259
|
+
await trpc.tasks.delete({ id: "123" });
|
|
260
|
+
```
|
|
261
|
+
|
|
262
|
+
### 6. `deleteMany` - 批量删除
|
|
263
|
+
|
|
264
|
+
**输入**:
|
|
265
|
+
```typescript
|
|
266
|
+
{ ids: string[] }
|
|
267
|
+
```
|
|
268
|
+
|
|
269
|
+
**输出**:
|
|
270
|
+
```typescript
|
|
271
|
+
void
|
|
272
|
+
```
|
|
273
|
+
|
|
274
|
+
**示例**:
|
|
275
|
+
```typescript
|
|
276
|
+
await trpc.tasks.deleteMany({ ids: ["1", "2", "3"] });
|
|
277
|
+
```
|
|
278
|
+
|
|
279
|
+
---
|
|
280
|
+
|
|
281
|
+
## 🎯 高级过滤
|
|
282
|
+
|
|
283
|
+
### 支持的操作符
|
|
284
|
+
|
|
285
|
+
| 操作符 | 说明 | 示例 |
|
|
286
|
+
|--------|------|------|
|
|
287
|
+
| `eq` | 等于 | `status = "done"` |
|
|
288
|
+
| `ne` | 不等于 | `status != "canceled"` |
|
|
289
|
+
| `gt` | 大于 | `estimatedHours > 5` |
|
|
290
|
+
| `gte` | 大于等于 | `estimatedHours >= 5` |
|
|
291
|
+
| `lt` | 小于 | `estimatedHours < 10` |
|
|
292
|
+
| `lte` | 小于等于 | `estimatedHours <= 10` |
|
|
293
|
+
| `like` | 包含 | `title LIKE "%bug%"` |
|
|
294
|
+
| `notLike` | 不包含 | `title NOT LIKE "%test%"` |
|
|
295
|
+
| `in` | 在列表中 | `status IN ["todo", "in-progress"]` |
|
|
296
|
+
| `notIn` | 不在列表中 | `status NOT IN ["canceled"]` |
|
|
297
|
+
| `between` | 范围 | `createdAt BETWEEN "2024-01-01" AND "2024-12-31"` |
|
|
298
|
+
| `isNull` | 为空 | `description IS NULL` |
|
|
299
|
+
| `isNotNull` | 不为空 | `description IS NOT NULL` |
|
|
300
|
+
|
|
301
|
+
### 过滤示例
|
|
302
|
+
|
|
303
|
+
#### 单条件过滤
|
|
304
|
+
|
|
305
|
+
```typescript
|
|
306
|
+
await trpc.tasks.list({
|
|
307
|
+
page: 1,
|
|
308
|
+
perPage: 10,
|
|
309
|
+
filters: [
|
|
310
|
+
{ id: "status", value: "done", operator: "eq", variant: "select" },
|
|
311
|
+
],
|
|
312
|
+
});
|
|
313
|
+
```
|
|
314
|
+
|
|
315
|
+
#### 多条件 AND 过滤
|
|
316
|
+
|
|
317
|
+
```typescript
|
|
318
|
+
await trpc.tasks.list({
|
|
319
|
+
page: 1,
|
|
320
|
+
perPage: 10,
|
|
321
|
+
filters: [
|
|
322
|
+
{ id: "status", value: "done", operator: "eq", variant: "select" },
|
|
323
|
+
{ id: "priority", value: "high", operator: "eq", variant: "select" },
|
|
324
|
+
],
|
|
325
|
+
joinOperator: "and", // status = "done" AND priority = "high"
|
|
326
|
+
});
|
|
327
|
+
```
|
|
328
|
+
|
|
329
|
+
#### 多条件 OR 过滤
|
|
330
|
+
|
|
331
|
+
```typescript
|
|
332
|
+
await trpc.tasks.list({
|
|
333
|
+
page: 1,
|
|
334
|
+
perPage: 10,
|
|
335
|
+
filters: [
|
|
336
|
+
{ id: "status", value: "todo", operator: "eq", variant: "select" },
|
|
337
|
+
{ id: "status", value: "in-progress", operator: "eq", variant: "select" },
|
|
338
|
+
],
|
|
339
|
+
joinOperator: "or", // status = "todo" OR status = "in-progress"
|
|
340
|
+
});
|
|
341
|
+
```
|
|
342
|
+
|
|
343
|
+
#### 范围过滤
|
|
344
|
+
|
|
345
|
+
```typescript
|
|
346
|
+
await trpc.tasks.list({
|
|
347
|
+
page: 1,
|
|
348
|
+
perPage: 10,
|
|
349
|
+
filters: [
|
|
350
|
+
{
|
|
351
|
+
id: "createdAt",
|
|
352
|
+
value: ["2024-01-01", "2024-12-31"],
|
|
353
|
+
operator: "between",
|
|
354
|
+
variant: "dateRange",
|
|
355
|
+
},
|
|
356
|
+
],
|
|
357
|
+
});
|
|
358
|
+
```
|
|
359
|
+
|
|
360
|
+
#### 模糊搜索
|
|
361
|
+
|
|
362
|
+
```typescript
|
|
363
|
+
await trpc.tasks.list({
|
|
364
|
+
page: 1,
|
|
365
|
+
perPage: 10,
|
|
366
|
+
filters: [
|
|
367
|
+
{ id: "title", value: "bug", operator: "like", variant: "text" },
|
|
368
|
+
],
|
|
369
|
+
});
|
|
370
|
+
```
|
|
371
|
+
|
|
372
|
+
---
|
|
373
|
+
|
|
374
|
+
## 🔧 API 参考
|
|
375
|
+
|
|
376
|
+
### `createCrudRouter(config)`
|
|
377
|
+
|
|
378
|
+
创建 CRUD 路由。
|
|
379
|
+
|
|
380
|
+
#### 参数
|
|
381
|
+
|
|
382
|
+
```typescript
|
|
383
|
+
interface CrudRouterConfig<TTable, TSelect, TInsert, TUpdate> {
|
|
384
|
+
table: TTable; // Drizzle 表定义
|
|
385
|
+
selectSchema: z.ZodType<TSelect>; // 查询返回 Schema
|
|
386
|
+
insertSchema: z.ZodType<TInsert>; // 创建输入 Schema
|
|
387
|
+
updateSchema: z.ZodType<TUpdate>; // 更新输入 Schema
|
|
388
|
+
idField?: string; // ID 字段名,默认 "id"
|
|
389
|
+
}
|
|
390
|
+
```
|
|
391
|
+
|
|
392
|
+
#### 返回值
|
|
393
|
+
|
|
394
|
+
```typescript
|
|
395
|
+
{
|
|
396
|
+
list: Procedure<ListInput, ListOutput>,
|
|
397
|
+
get: Procedure<{ id: string }, TSelect>,
|
|
398
|
+
create: Procedure<TInsert, TSelect>,
|
|
399
|
+
update: Procedure<{ id: string, data: TUpdate }, TSelect>,
|
|
400
|
+
delete: Procedure<{ id: string }, void>,
|
|
401
|
+
deleteMany: Procedure<{ ids: string[] }, void>,
|
|
402
|
+
}
|
|
403
|
+
```
|
|
404
|
+
|
|
405
|
+
#### 完整示例
|
|
406
|
+
|
|
407
|
+
```typescript
|
|
408
|
+
import { createCrudRouter } from "@wordrhyme/auto-crud-server";
|
|
409
|
+
import { users } from "@/db/schema";
|
|
410
|
+
import { createSelectSchema } from "drizzle-zod";
|
|
411
|
+
import { z } from "zod";
|
|
412
|
+
|
|
413
|
+
const selectUserSchema = createSelectSchema(users);
|
|
414
|
+
|
|
415
|
+
const insertUserSchema = selectUserSchema.omit({
|
|
416
|
+
id: true,
|
|
417
|
+
createdAt: true,
|
|
418
|
+
updatedAt: true,
|
|
419
|
+
});
|
|
420
|
+
|
|
421
|
+
const updateUserSchema = insertUserSchema.partial();
|
|
422
|
+
|
|
423
|
+
export const usersRouter = createCrudRouter({
|
|
424
|
+
table: users,
|
|
425
|
+
selectSchema: selectUserSchema,
|
|
426
|
+
insertSchema: insertUserSchema,
|
|
427
|
+
updateSchema: updateUserSchema,
|
|
428
|
+
idField: "id",
|
|
429
|
+
});
|
|
430
|
+
```
|
|
431
|
+
|
|
432
|
+
---
|
|
433
|
+
|
|
434
|
+
## 🔐 权限控制
|
|
435
|
+
|
|
436
|
+
### 使用 tRPC Middleware
|
|
437
|
+
|
|
438
|
+
```typescript
|
|
439
|
+
import { initTRPC, TRPCError } from "@trpc/server";
|
|
440
|
+
|
|
441
|
+
const t = initTRPC.context<Context>().create();
|
|
442
|
+
|
|
443
|
+
// 创建受保护的 procedure
|
|
444
|
+
const protectedProcedure = t.procedure.use(async ({ ctx, next }) => {
|
|
445
|
+
if (!ctx.user) {
|
|
446
|
+
throw new TRPCError({ code: "UNAUTHORIZED" });
|
|
447
|
+
}
|
|
448
|
+
return next({ ctx: { ...ctx, user: ctx.user } });
|
|
449
|
+
});
|
|
450
|
+
|
|
451
|
+
// 在 createCrudRouter 中使用
|
|
452
|
+
export const tasksRouter = createCrudRouter({
|
|
453
|
+
table: tasks,
|
|
454
|
+
selectSchema: selectTaskSchema,
|
|
455
|
+
insertSchema: insertTaskSchema,
|
|
456
|
+
updateSchema: updateTaskSchema,
|
|
457
|
+
// 注意:当前版本不支持直接传入 procedure
|
|
458
|
+
// 需要在路由层面添加权限控制
|
|
459
|
+
});
|
|
460
|
+
|
|
461
|
+
// 包装路由添加权限
|
|
462
|
+
export const protectedTasksRouter = {
|
|
463
|
+
list: protectedProcedure.query(async ({ input, ctx }) => {
|
|
464
|
+
return tasksRouter.list(input, ctx);
|
|
465
|
+
}),
|
|
466
|
+
// ... 其他路由
|
|
467
|
+
};
|
|
468
|
+
```
|
|
469
|
+
|
|
470
|
+
### 行级权限控制
|
|
471
|
+
|
|
472
|
+
```typescript
|
|
473
|
+
// 在 createContext 中添加用户信息
|
|
474
|
+
export const createContext = async ({ req }: { req: Request }) => {
|
|
475
|
+
const user = await getUserFromRequest(req);
|
|
476
|
+
return { db, user };
|
|
477
|
+
};
|
|
478
|
+
|
|
479
|
+
// 在查询中添加过滤条件
|
|
480
|
+
const result = await db
|
|
481
|
+
.select()
|
|
482
|
+
.from(tasks)
|
|
483
|
+
.where(eq(tasks.userId, ctx.user.id)); // 只查询当前用户的任务
|
|
484
|
+
```
|
|
485
|
+
|
|
486
|
+
---
|
|
487
|
+
|
|
488
|
+
## 🎨 自定义扩展
|
|
489
|
+
|
|
490
|
+
### 添加自定义路由
|
|
491
|
+
|
|
492
|
+
```typescript
|
|
493
|
+
import { createCrudRouter } from "@wordrhyme/auto-crud-server";
|
|
494
|
+
import { publicProcedure } from "../trpc";
|
|
495
|
+
|
|
496
|
+
const baseCrudRouter = createCrudRouter({
|
|
497
|
+
table: tasks,
|
|
498
|
+
selectSchema: selectTaskSchema,
|
|
499
|
+
insertSchema: insertTaskSchema,
|
|
500
|
+
updateSchema: updateTaskSchema,
|
|
501
|
+
});
|
|
502
|
+
|
|
503
|
+
export const tasksRouter = {
|
|
504
|
+
...baseCrudRouter,
|
|
505
|
+
|
|
506
|
+
// 添加自定义路由
|
|
507
|
+
archive: publicProcedure
|
|
508
|
+
.input(z.object({ id: z.string() }))
|
|
509
|
+
.mutation(async ({ input, ctx }) => {
|
|
510
|
+
return ctx.db
|
|
511
|
+
.update(tasks)
|
|
512
|
+
.set({ archived: true })
|
|
513
|
+
.where(eq(tasks.id, input.id));
|
|
514
|
+
}),
|
|
515
|
+
|
|
516
|
+
unarchive: publicProcedure
|
|
517
|
+
.input(z.object({ id: z.string() }))
|
|
518
|
+
.mutation(async ({ input, ctx }) => {
|
|
519
|
+
return ctx.db
|
|
520
|
+
.update(tasks)
|
|
521
|
+
.set({ archived: false })
|
|
522
|
+
.where(eq(tasks.id, input.id));
|
|
523
|
+
}),
|
|
524
|
+
};
|
|
525
|
+
```
|
|
526
|
+
|
|
527
|
+
### 自定义过滤逻辑
|
|
528
|
+
|
|
529
|
+
```typescript
|
|
530
|
+
// 在 createCrudRouter 之前预处理过滤条件
|
|
531
|
+
const customList = publicProcedure
|
|
532
|
+
.input(listInputSchema)
|
|
533
|
+
.query(async ({ input, ctx }) => {
|
|
534
|
+
// 自定义过滤逻辑
|
|
535
|
+
const customFilters = input.filters?.map(filter => {
|
|
536
|
+
if (filter.id === "customField") {
|
|
537
|
+
// 自定义处理
|
|
538
|
+
return { ...filter, operator: "custom" };
|
|
539
|
+
}
|
|
540
|
+
return filter;
|
|
541
|
+
});
|
|
542
|
+
|
|
543
|
+
return baseCrudRouter.list({
|
|
544
|
+
...input,
|
|
545
|
+
filters: customFilters,
|
|
546
|
+
}, ctx);
|
|
547
|
+
});
|
|
548
|
+
```
|
|
549
|
+
|
|
550
|
+
---
|
|
551
|
+
|
|
552
|
+
## 🔌 与其他库集成
|
|
553
|
+
|
|
554
|
+
### 与 Drizzle ORM 集成
|
|
555
|
+
|
|
556
|
+
```typescript
|
|
557
|
+
import { drizzle } from "drizzle-orm/postgres-js";
|
|
558
|
+
import postgres from "postgres";
|
|
559
|
+
import * as schema from "./schema";
|
|
560
|
+
|
|
561
|
+
const client = postgres(process.env.DATABASE_URL!);
|
|
562
|
+
export const db = drizzle(client, { schema });
|
|
563
|
+
|
|
564
|
+
// 在 createContext 中传递 db
|
|
565
|
+
export const createContext = () => ({ db });
|
|
566
|
+
```
|
|
567
|
+
|
|
568
|
+
### 与 Next.js 集成
|
|
569
|
+
|
|
570
|
+
```typescript
|
|
571
|
+
// app/api/trpc/[trpc]/route.ts
|
|
572
|
+
import { fetchRequestHandler } from "@trpc/server/adapters/fetch";
|
|
573
|
+
import { appRouter } from "@/server/routers";
|
|
574
|
+
|
|
575
|
+
const handler = (req: Request) =>
|
|
576
|
+
fetchRequestHandler({
|
|
577
|
+
endpoint: "/api/trpc",
|
|
578
|
+
req,
|
|
579
|
+
router: appRouter,
|
|
580
|
+
createContext: () => ({ db }),
|
|
581
|
+
});
|
|
582
|
+
|
|
583
|
+
export { handler as GET, handler as POST };
|
|
584
|
+
```
|
|
585
|
+
|
|
586
|
+
### 与 Express 集成
|
|
587
|
+
|
|
588
|
+
```typescript
|
|
589
|
+
import express from "express";
|
|
590
|
+
import { createExpressMiddleware } from "@trpc/server/adapters/express";
|
|
591
|
+
import { appRouter } from "./server/routers";
|
|
592
|
+
|
|
593
|
+
const app = express();
|
|
594
|
+
|
|
595
|
+
app.use(
|
|
596
|
+
"/trpc",
|
|
597
|
+
createExpressMiddleware({
|
|
598
|
+
router: appRouter,
|
|
599
|
+
createContext: () => ({ db }),
|
|
600
|
+
})
|
|
601
|
+
);
|
|
602
|
+
|
|
603
|
+
app.listen(3000);
|
|
604
|
+
```
|
|
605
|
+
|
|
606
|
+
---
|
|
607
|
+
|
|
608
|
+
## 🛠️ 配置选项
|
|
609
|
+
|
|
610
|
+
### 自定义 ID 字段
|
|
611
|
+
|
|
612
|
+
```typescript
|
|
613
|
+
export const usersRouter = createCrudRouter({
|
|
614
|
+
table: users,
|
|
615
|
+
selectSchema: selectUserSchema,
|
|
616
|
+
insertSchema: insertUserSchema,
|
|
617
|
+
updateSchema: updateUserSchema,
|
|
618
|
+
idField: "userId", // 使用自定义 ID 字段
|
|
619
|
+
});
|
|
620
|
+
```
|
|
621
|
+
|
|
622
|
+
### 自定义过滤器配置
|
|
623
|
+
|
|
624
|
+
```typescript
|
|
625
|
+
// src/config/data-table.ts
|
|
626
|
+
export const filterVariants = {
|
|
627
|
+
text: ["eq", "ne", "like", "notLike"],
|
|
628
|
+
number: ["eq", "ne", "gt", "gte", "lt", "lte", "between"],
|
|
629
|
+
select: ["eq", "ne", "in", "notIn"],
|
|
630
|
+
date: ["eq", "ne", "gt", "gte", "lt", "lte", "between"],
|
|
631
|
+
boolean: ["eq"],
|
|
632
|
+
};
|
|
633
|
+
```
|
|
634
|
+
|
|
635
|
+
---
|
|
636
|
+
|
|
637
|
+
## 📦 导出的类型
|
|
638
|
+
|
|
639
|
+
```typescript
|
|
640
|
+
// 主要导出
|
|
641
|
+
export { createCrudRouter } from "./routers/_factory";
|
|
642
|
+
export type { CrudRouterConfig } from "./routers/_factory";
|
|
643
|
+
|
|
644
|
+
// tRPC 工具
|
|
645
|
+
export { router, publicProcedure } from "./trpc";
|
|
646
|
+
|
|
647
|
+
// 示例路由(可选)
|
|
648
|
+
export { appRouter } from "./routers";
|
|
649
|
+
export type { AppRouter } from "./routers";
|
|
650
|
+
```
|
|
651
|
+
|
|
652
|
+
---
|
|
653
|
+
|
|
654
|
+
## 🐛 故障排除
|
|
655
|
+
|
|
656
|
+
### 问题 1: 模块找不到
|
|
657
|
+
|
|
658
|
+
**错误**: `Cannot find module '@wordrhyme/auto-crud-server'`
|
|
659
|
+
|
|
660
|
+
**解决方案**:
|
|
661
|
+
```bash
|
|
662
|
+
pnpm install @wordrhyme/auto-crud-server
|
|
663
|
+
```
|
|
664
|
+
|
|
665
|
+
### 问题 2: 类型错误
|
|
666
|
+
|
|
667
|
+
**错误**: `Type 'X' is not assignable to type 'Y'`
|
|
668
|
+
|
|
669
|
+
**解决方案**: 确保 Zod 版本一致
|
|
670
|
+
```bash
|
|
671
|
+
pnpm list zod
|
|
672
|
+
# 确保所有包使用相同的 Zod 版本
|
|
673
|
+
```
|
|
674
|
+
|
|
675
|
+
### 问题 3: 数据库连接失败
|
|
676
|
+
|
|
677
|
+
**错误**: `connect ECONNREFUSED`
|
|
678
|
+
|
|
679
|
+
**解决方案**: 检查数据库连接字符串
|
|
680
|
+
```typescript
|
|
681
|
+
// .env
|
|
682
|
+
DATABASE_URL="postgresql://user:password@localhost:5432/dbname"
|
|
683
|
+
```
|
|
684
|
+
|
|
685
|
+
---
|
|
686
|
+
|
|
687
|
+
## 🤝 贡献
|
|
688
|
+
|
|
689
|
+
欢迎贡献代码!请查看 [CONTRIBUTING.md](../../CONTRIBUTING.md)。
|
|
690
|
+
|
|
691
|
+
---
|
|
692
|
+
|
|
693
|
+
## 📄 许可证
|
|
694
|
+
|
|
695
|
+
MIT © [wordrhyme](https://github.com/pixpilot/shadcn-components)
|
|
696
|
+
|
|
697
|
+
---
|
|
698
|
+
|
|
699
|
+
## 🔗 相关链接
|
|
700
|
+
|
|
701
|
+
- [GitHub](https://github.com/pixpilot/shadcn-components)
|
|
702
|
+
- [文档](https://github.com/pixpilot/shadcn-components/tree/main/packages/auto-crud-server)
|
|
703
|
+
- [@wordrhyme/auto-crud](https://github.com/pixpilot/shadcn-components/tree/main/packages/auto-crud) - 前端组件库
|
|
704
|
+
- [Changelog](./CHANGELOG.md)
|
|
705
|
+
|
|
706
|
+
---
|
|
707
|
+
|
|
708
|
+
## 💡 灵感来源
|
|
709
|
+
|
|
710
|
+
- [tRPC](https://trpc.io/) - End-to-end typesafe APIs
|
|
711
|
+
- [Drizzle ORM](https://orm.drizzle.team/) - TypeScript ORM
|
|
712
|
+
- [Prisma](https://www.prisma.io/) - Next-generation ORM
|