@wordrhyme/auto-crud-server 1.0.4 → 1.0.5

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/README.md CHANGED
@@ -52,20 +52,24 @@ yarn add @wordrhyme/auto-crud-server
52
52
 
53
53
  ```typescript
54
54
  // src/db/schema.ts
55
- import { pgTable, varchar, real, boolean, timestamp } from "drizzle-orm/pg-core";
55
+ import { pgTable, varchar, real, boolean, timestamp } from 'drizzle-orm/pg-core';
56
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(),
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
+ })
63
+ .notNull()
64
+ .default('todo'),
65
+ priority: varchar('priority', {
66
+ enum: ['low', 'medium', 'high'],
67
+ })
68
+ .notNull()
69
+ .default('low'),
70
+ estimatedHours: real('estimated_hours').default(0),
71
+ createdAt: timestamp('created_at').defaultNow().notNull(),
72
+ updatedAt: timestamp('updated_at').defaultNow(),
69
73
  });
70
74
  ```
71
75
 
@@ -73,8 +77,8 @@ export const tasks = pgTable("tasks", {
73
77
 
74
78
  ```typescript
75
79
  // src/server/routers/tasks.ts
76
- import { createCrudRouter } from "@wordrhyme/auto-crud-server";
77
- import { tasks } from "@/db/schema";
80
+ import { createCrudRouter } from '@wordrhyme/auto-crud-server';
81
+ import { tasks } from '@/db/schema';
78
82
 
79
83
  // 🚀 零配置!一行代码生成完整 CRUD 路由
80
84
  export const tasksRouter = createCrudRouter({
@@ -86,9 +90,9 @@ export const tasksRouter = createCrudRouter({
86
90
  **或者,显式传入 Schema:**
87
91
 
88
92
  ```typescript
89
- import { createCrudRouter } from "@wordrhyme/auto-crud-server";
90
- import { tasks } from "@/db/schema";
91
- import { createSelectSchema } from "drizzle-zod";
93
+ import { createCrudRouter } from '@wordrhyme/auto-crud-server';
94
+ import { tasks } from '@/db/schema';
95
+ import { createSelectSchema } from 'drizzle-zod';
92
96
 
93
97
  // 从 Drizzle Schema 自动生成 Zod Schema
94
98
  const taskSchema = createSelectSchema(tasks).omit({
@@ -99,7 +103,7 @@ const taskSchema = createSelectSchema(tasks).omit({
99
103
 
100
104
  export const tasksRouter = createCrudRouter({
101
105
  table: tasks,
102
- schema: taskSchema, // 主 Schema(用于 create/upsert)
106
+ schema: taskSchema, // 主 Schema(用于 create/upsert)
103
107
  // updateSchema 自动派生为 schema.partial()
104
108
  });
105
109
  ```
@@ -108,8 +112,8 @@ export const tasksRouter = createCrudRouter({
108
112
 
109
113
  ```typescript
110
114
  // src/server/routers/index.ts
111
- import { router } from "../trpc";
112
- import { tasksRouter } from "./tasks";
115
+ import { router } from '../trpc';
116
+ import { tasksRouter } from './tasks';
113
117
 
114
118
  export const appRouter = router({
115
119
  tasks: tasksRouter,
@@ -122,13 +126,13 @@ export type AppRouter = typeof appRouter;
122
126
 
123
127
  ```typescript
124
128
  // src/app/api/trpc/[trpc]/route.ts (Next.js App Router)
125
- import { fetchRequestHandler } from "@trpc/server/adapters/fetch";
126
- import { appRouter } from "@/server/routers";
127
- import { db } from "@/db";
129
+ import { fetchRequestHandler } from '@trpc/server/adapters/fetch';
130
+ import { appRouter } from '@/server/routers';
131
+ import { db } from '@/db';
128
132
 
129
133
  const handler = (req: Request) =>
130
134
  fetchRequestHandler({
131
- endpoint: "/api/trpc",
135
+ endpoint: '/api/trpc',
132
136
  req,
133
137
  router: appRouter,
134
138
  createContext: () => ({ db }),
@@ -146,6 +150,7 @@ export { handler as GET, handler as POST };
146
150
  ### 1. `list` - 列表查询
147
151
 
148
152
  **输入**:
153
+
149
154
  ```typescript
150
155
  {
151
156
  page: number; // 页码(从 1 开始)
@@ -165,6 +170,7 @@ export { handler as GET, handler as POST };
165
170
  ```
166
171
 
167
172
  **输出**:
173
+
168
174
  ```typescript
169
175
  {
170
176
  data: Task[]; // 数据列表
@@ -173,117 +179,138 @@ export { handler as GET, handler as POST };
173
179
  ```
174
180
 
175
181
  **示例**:
182
+
176
183
  ```typescript
177
184
  const result = await trpc.tasks.list({
178
185
  page: 1,
179
186
  perPage: 10,
180
- sort: [{ id: "createdAt", desc: true }],
187
+ sort: [{ id: 'createdAt', desc: true }],
181
188
  filters: [
182
- { id: "status", value: "done", operator: "eq", variant: "select" },
183
- { id: "priority", value: "high", operator: "eq", variant: "select" },
189
+ { id: 'status', value: 'done', operator: 'eq', variant: 'select' },
190
+ { id: 'priority', value: 'high', operator: 'eq', variant: 'select' },
184
191
  ],
185
- joinOperator: "and",
192
+ joinOperator: 'and',
186
193
  });
187
194
  ```
188
195
 
189
196
  ### 2. `get` - 单条查询
190
197
 
191
198
  **输入**:
199
+
192
200
  ```typescript
193
- { id: string }
201
+ {
202
+ id: string;
203
+ }
194
204
  ```
195
205
 
196
206
  **输出**:
207
+
197
208
  ```typescript
198
- Task
209
+ Task;
199
210
  ```
200
211
 
201
212
  **示例**:
213
+
202
214
  ```typescript
203
- const task = await trpc.tasks.get({ id: "123" });
215
+ const task = await trpc.tasks.get({ id: '123' });
204
216
  ```
205
217
 
206
218
  ### 3. `create` - 创建
207
219
 
208
220
  **输入**:
221
+
209
222
  ```typescript
210
- Omit<Task, "id" | "createdAt" | "updatedAt">
223
+ Omit<Task, 'id' | 'createdAt' | 'updatedAt'>;
211
224
  ```
212
225
 
213
226
  **输出**:
227
+
214
228
  ```typescript
215
- Task
229
+ Task;
216
230
  ```
217
231
 
218
232
  **示例**:
233
+
219
234
  ```typescript
220
235
  const newTask = await trpc.tasks.create({
221
- title: "New Task",
222
- status: "todo",
223
- priority: "high",
236
+ title: 'New Task',
237
+ status: 'todo',
238
+ priority: 'high',
224
239
  });
225
240
  ```
226
241
 
227
242
  ### 4. `update` - 更新
228
243
 
229
244
  **输入**:
245
+
230
246
  ```typescript
231
247
  {
232
248
  id: string;
233
- data: Partial<Omit<Task, "id" | "createdAt" | "updatedAt">>;
249
+ data: Partial<Omit<Task, 'id' | 'createdAt' | 'updatedAt'>>;
234
250
  }
235
251
  ```
236
252
 
237
253
  **输出**:
254
+
238
255
  ```typescript
239
- Task
256
+ Task;
240
257
  ```
241
258
 
242
259
  **示例**:
260
+
243
261
  ```typescript
244
262
  const updatedTask = await trpc.tasks.update({
245
- id: "123",
246
- data: { status: "done" },
263
+ id: '123',
264
+ data: { status: 'done' },
247
265
  });
248
266
  ```
249
267
 
250
268
  ### 5. `delete` - 删除
251
269
 
252
270
  **输入**:
271
+
253
272
  ```typescript
254
- { id: string }
273
+ {
274
+ id: string;
275
+ }
255
276
  ```
256
277
 
257
278
  **输出**:
279
+
258
280
  ```typescript
259
281
  void
260
282
  ```
261
283
 
262
284
  **示例**:
285
+
263
286
  ```typescript
264
- await trpc.tasks.delete({ id: "123" });
287
+ await trpc.tasks.delete({ id: '123' });
265
288
  ```
266
289
 
267
290
  ### 6. `deleteMany` - 批量删除
268
291
 
269
292
  **输入**:
293
+
270
294
  ```typescript
271
295
  { ids: string[] }
272
296
  ```
273
297
 
274
298
  **输出**:
299
+
275
300
  ```typescript
276
301
  void
277
302
  ```
278
303
 
279
304
  **示例**:
305
+
280
306
  ```typescript
281
- await trpc.tasks.deleteMany({ ids: ["1", "2", "3"] });
307
+ await trpc.tasks.deleteMany({ ids: ['1', '2', '3'] });
282
308
  ```
283
309
 
284
310
  ### 7. `updateMany` - 批量更新
285
311
 
286
312
  **输入**:
313
+
287
314
  ```typescript
288
315
  {
289
316
  ids: string[];
@@ -292,50 +319,58 @@ await trpc.tasks.deleteMany({ ids: ["1", "2", "3"] });
292
319
  ```
293
320
 
294
321
  **输出**:
322
+
295
323
  ```typescript
296
- { updated: number }
324
+ {
325
+ updated: number;
326
+ }
297
327
  ```
298
328
 
299
329
  **示例**:
330
+
300
331
  ```typescript
301
332
  await trpc.tasks.updateMany({
302
- ids: ["1", "2", "3"],
303
- data: { status: "done" },
333
+ ids: ['1', '2', '3'],
334
+ data: { status: 'done' },
304
335
  });
305
336
  ```
306
337
 
307
338
  ### 8. `upsert` - 存在则更新,不存在则创建
308
339
 
309
340
  **输入**:
341
+
310
342
  ```typescript
311
- Omit<Task, "createdAt" | "updatedAt"> // 需要包含 id
343
+ Omit<Task, 'createdAt' | 'updatedAt'>; // 需要包含 id
312
344
  ```
313
345
 
314
346
  **输出**:
347
+
315
348
  ```typescript
316
349
  {
317
- data: Task; // 创建或更新后的记录
318
- isNew: boolean; // true = 新建, false = 更新
350
+ data: Task; // 创建或更新后的记录
351
+ isNew: boolean; // true = 新建, false = 更新
319
352
  }
320
353
  ```
321
354
 
322
355
  **示例**:
356
+
323
357
  ```typescript
324
358
  // 如果 id="123" 存在则更新,不存在则创建
325
359
  const { data, isNew } = await trpc.tasks.upsert({
326
- id: "123",
327
- title: "My Task",
328
- status: "todo",
360
+ id: '123',
361
+ title: 'My Task',
362
+ status: 'todo',
329
363
  });
330
364
 
331
365
  if (isNew) {
332
- console.log("Created new task");
366
+ console.log('Created new task');
333
367
  } else {
334
- console.log("Updated existing task");
368
+ console.log('Updated existing task');
335
369
  }
336
370
  ```
337
371
 
338
372
  **适用场景**:
373
+
339
374
  - 同步外部数据
340
375
  - 幂等导入
341
376
  - 配置项更新
@@ -346,21 +381,21 @@ if (isNew) {
346
381
 
347
382
  ### 支持的操作符
348
383
 
349
- | 操作符 | 说明 | 示例 |
350
- |--------|------|------|
351
- | `eq` | 等于 | `status = "done"` |
352
- | `ne` | 不等于 | `status != "canceled"` |
353
- | `gt` | 大于 | `estimatedHours > 5` |
354
- | `gte` | 大于等于 | `estimatedHours >= 5` |
355
- | `lt` | 小于 | `estimatedHours < 10` |
356
- | `lte` | 小于等于 | `estimatedHours <= 10` |
357
- | `like` | 包含 | `title LIKE "%bug%"` |
358
- | `notLike` | 不包含 | `title NOT LIKE "%test%"` |
359
- | `in` | 在列表中 | `status IN ["todo", "in-progress"]` |
360
- | `notIn` | 不在列表中 | `status NOT IN ["canceled"]` |
361
- | `between` | 范围 | `createdAt BETWEEN "2024-01-01" AND "2024-12-31"` |
362
- | `isNull` | 为空 | `description IS NULL` |
363
- | `isNotNull` | 不为空 | `description IS NOT NULL` |
384
+ | 操作符 | 说明 | 示例 |
385
+ | ----------- | ---------- | ------------------------------------------------- |
386
+ | `eq` | 等于 | `status = "done"` |
387
+ | `ne` | 不等于 | `status != "canceled"` |
388
+ | `gt` | 大于 | `estimatedHours > 5` |
389
+ | `gte` | 大于等于 | `estimatedHours >= 5` |
390
+ | `lt` | 小于 | `estimatedHours < 10` |
391
+ | `lte` | 小于等于 | `estimatedHours <= 10` |
392
+ | `like` | 包含 | `title LIKE "%bug%"` |
393
+ | `notLike` | 不包含 | `title NOT LIKE "%test%"` |
394
+ | `in` | 在列表中 | `status IN ["todo", "in-progress"]` |
395
+ | `notIn` | 不在列表中 | `status NOT IN ["canceled"]` |
396
+ | `between` | 范围 | `createdAt BETWEEN "2024-01-01" AND "2024-12-31"` |
397
+ | `isNull` | 为空 | `description IS NULL` |
398
+ | `isNotNull` | 不为空 | `description IS NOT NULL` |
364
399
 
365
400
  ### 过滤示例
366
401
 
@@ -370,9 +405,7 @@ if (isNew) {
370
405
  await trpc.tasks.list({
371
406
  page: 1,
372
407
  perPage: 10,
373
- filters: [
374
- { id: "status", value: "done", operator: "eq", variant: "select" },
375
- ],
408
+ filters: [{ id: 'status', value: 'done', operator: 'eq', variant: 'select' }],
376
409
  });
377
410
  ```
378
411
 
@@ -383,10 +416,10 @@ await trpc.tasks.list({
383
416
  page: 1,
384
417
  perPage: 10,
385
418
  filters: [
386
- { id: "status", value: "done", operator: "eq", variant: "select" },
387
- { id: "priority", value: "high", operator: "eq", variant: "select" },
419
+ { id: 'status', value: 'done', operator: 'eq', variant: 'select' },
420
+ { id: 'priority', value: 'high', operator: 'eq', variant: 'select' },
388
421
  ],
389
- joinOperator: "and", // status = "done" AND priority = "high"
422
+ joinOperator: 'and', // status = "done" AND priority = "high"
390
423
  });
391
424
  ```
392
425
 
@@ -397,10 +430,10 @@ await trpc.tasks.list({
397
430
  page: 1,
398
431
  perPage: 10,
399
432
  filters: [
400
- { id: "status", value: "todo", operator: "eq", variant: "select" },
401
- { id: "status", value: "in-progress", operator: "eq", variant: "select" },
433
+ { id: 'status', value: 'todo', operator: 'eq', variant: 'select' },
434
+ { id: 'status', value: 'in-progress', operator: 'eq', variant: 'select' },
402
435
  ],
403
- joinOperator: "or", // status = "todo" OR status = "in-progress"
436
+ joinOperator: 'or', // status = "todo" OR status = "in-progress"
404
437
  });
405
438
  ```
406
439
 
@@ -412,10 +445,10 @@ await trpc.tasks.list({
412
445
  perPage: 10,
413
446
  filters: [
414
447
  {
415
- id: "createdAt",
416
- value: ["2024-01-01", "2024-12-31"],
417
- operator: "between",
418
- variant: "dateRange",
448
+ id: 'createdAt',
449
+ value: ['2024-01-01', '2024-12-31'],
450
+ operator: 'between',
451
+ variant: 'dateRange',
419
452
  },
420
453
  ],
421
454
  });
@@ -427,9 +460,7 @@ await trpc.tasks.list({
427
460
  await trpc.tasks.list({
428
461
  page: 1,
429
462
  perPage: 10,
430
- filters: [
431
- { id: "title", value: "bug", operator: "like", variant: "text" },
432
- ],
463
+ filters: [{ id: 'title', value: 'bug', operator: 'like', variant: 'text' }],
433
464
  });
434
465
  ```
435
466
 
@@ -446,25 +477,28 @@ await trpc.tasks.list({
446
477
  ```typescript
447
478
  interface CrudRouterConfig<TTable, TSelect, TInsert, TUpdate> {
448
479
  // ========== 必填 ==========
449
- table: TTable; // Drizzle 表定义
480
+ table: TTable; // Drizzle 表定义
450
481
 
451
482
  // ========== Schema 配置(可选) ==========
452
- schema?: z.ZodType<TInsert>; // 主 Schema(用于 create/upsert)
453
- updateSchema?: z.ZodType<TUpdate>;// 更新 Schema(覆盖自动派生)
454
- selectSchema?: z.ZodType<TSelect>;// 查询返回 Schema
483
+ schema?: z.ZodType<TInsert>; // 主 Schema(用于 create/upsert)
484
+ updateSchema?: z.ZodType<TUpdate>; // 更新 Schema(覆盖自动派生)
485
+ selectSchema?: z.ZodType<TSelect>; // 查询返回 Schema
486
+ listInputSchema?: z.ZodType; // list 输入 Schema(可扩展)
487
+ getInputSchema?: z.ZodType; // get 输入 Schema(可扩展)
488
+ exportInputSchema?: z.ZodType; // export 输入 Schema(可扩展)
455
489
 
456
490
  // ========== 其他配置 ==========
457
- idField?: string; // ID 字段名,默认 "id"
458
- omitFields?: string[]; // 自动派生时排除的字段
459
- // 默认 ["id", "createdAt", "updatedAt"]
491
+ idField?: string; // ID 字段名,默认 "id"
492
+ omitFields?: string[]; // 自动派生时排除的字段
493
+ // 默认 ["id", "createdAt", "updatedAt"]
460
494
  }
461
495
  ```
462
496
 
463
497
  #### Schema 派生规则
464
498
 
465
- | 配置 | 派生行为 |
466
- |-----|---------|
467
- | 无 `schema` | 从 `table` 自动派生,排除 `omitFields` |
499
+ | 配置 | 派生行为 |
500
+ | ----------------- | ------------------------------------------- |
501
+ | 无 `schema` | 从 `table` 自动派生,排除 `omitFields` |
468
502
  | 无 `updateSchema` | 从 `schema.partial().refine(nonEmpty)` 派生 |
469
503
  | 无 `selectSchema` | 若有 `schema` 则使用它,否则从 `table` 派生 |
470
504
 
@@ -492,10 +526,121 @@ const usersRouter = createCrudRouter({
492
526
  // 4. 自定义排除字段
493
527
  const ordersRouter = createCrudRouter({
494
528
  table: orders,
495
- omitFields: ["id", "createdAt", "updatedAt", "internalCode"],
529
+ omitFields: ['id', 'createdAt', 'updatedAt', 'internalCode'],
530
+ });
531
+ ```
532
+
533
+ ### 扩展读取输入
534
+
535
+ 默认 `list` 输入为 `baseListInputSchema`:
536
+
537
+ ```typescript
538
+ {
539
+ page: number;
540
+ perPage: number;
541
+ sort?: Array<{ id: string; desc: boolean }>;
542
+ filters?: Array<FilterItem>;
543
+ joinOperator: "and" | "or";
544
+ }
545
+ ```
546
+
547
+ 业务侧可以用 `baseListInputSchema.extend(...)` 增加自定义参数,不需要覆盖
548
+ `list` procedure。默认查询逻辑仍只读取分页、排序、过滤和 `joinOperator`,
549
+ 额外字段会保留在 `middleware.list` 的 `input` 中。
550
+
551
+ ```typescript
552
+ import { baseListInputSchema, createCrudRouter } from '@wordrhyme/auto-crud-server';
553
+ import { z } from 'zod';
554
+
555
+ const productsRouter = createCrudRouter({
556
+ table: shopProducts,
557
+ idField: 'spuId',
558
+ schema: createProductSchema,
559
+ updateSchema: updateProductSchema,
560
+ listInputSchema: baseListInputSchema.extend({
561
+ include: z
562
+ .object({
563
+ skus: z.boolean().optional(),
564
+ })
565
+ .optional(),
566
+ }),
567
+ middleware: {
568
+ list: async ({ ctx, input, next }) => {
569
+ const result = await next(input);
570
+ if (!input.include?.skus) return result;
571
+ return attachSkus(ctx, result);
572
+ },
573
+ },
574
+ });
575
+
576
+ // 调用方
577
+ await trpc.products.list.query({
578
+ page: 1,
579
+ perPage: 20,
580
+ include: { skus: true },
581
+ });
582
+ ```
583
+
584
+ `get` 也可以扩展。默认仍兼容 `get("spu_1")`,业务侧如需详情页按需挂载关联数据,可以改成对象输入:
585
+
586
+ ```typescript
587
+ import { baseGetInputSchema, createCrudRouter } from '@wordrhyme/auto-crud-server';
588
+ import { z } from 'zod';
589
+
590
+ const productsRouter = createCrudRouter({
591
+ table: shopProducts,
592
+ idField: 'spuId',
593
+ schema: createProductSchema,
594
+ updateSchema: updateProductSchema,
595
+ getInputSchema: baseGetInputSchema.extend({
596
+ include: z.object({ skus: z.boolean().optional() }).optional(),
597
+ }),
598
+ middleware: {
599
+ get: async ({ ctx, input, next }) => {
600
+ const product = await next(input);
601
+ if (!product || typeof input === 'string' || !input.include?.skus) {
602
+ return product;
603
+ }
604
+ return attachProductSkus(ctx, product);
605
+ },
606
+ },
607
+ });
608
+
609
+ await trpc.products.get.query({
610
+ id: 'spu_1',
611
+ include: { skus: true },
612
+ });
613
+ ```
614
+
615
+ `export` 支持同样的扩展方式,默认导出逻辑只读取 `sort`、`filters`、`joinOperator` 和 `limit`:
616
+
617
+ ```typescript
618
+ import { baseExportInputSchema, createCrudRouter } from '@wordrhyme/auto-crud-server';
619
+ import { z } from 'zod';
620
+
621
+ const productsRouter = createCrudRouter({
622
+ table: shopProducts,
623
+ schema: createProductSchema,
624
+ updateSchema: updateProductSchema,
625
+ exportInputSchema: baseExportInputSchema.extend({
626
+ format: z.enum(['csv', 'xlsx']).optional(),
627
+ }),
628
+ middleware: {
629
+ export: async ({ input, next }) => {
630
+ const result = await next(input);
631
+ return input.format === 'xlsx' ? toXlsxExport(result) : result;
632
+ },
633
+ },
634
+ });
635
+
636
+ await trpc.products.export.query({
637
+ limit: 1000,
638
+ format: 'xlsx',
496
639
  });
497
640
  ```
498
641
 
642
+ 写入类内置方法(`create`、`update`、`upsert`、`createMany`)不提供通用控制参数扩展;它们的输入字段会进入默认写入逻辑。需要 `dryRun`、`notify` 等控制参数时,建议单独设计 envelope 或自定义业务 procedure。
643
+
499
644
  #### 返回值
500
645
 
501
646
  ```typescript
@@ -507,7 +652,7 @@ const ordersRouter = createCrudRouter({
507
652
  // 可 spread 的 procedures 对象(用于扩展自定义路由)
508
653
  procedures: {
509
654
  list: Procedure<ListInput, ListOutput>,
510
- get: Procedure<string, TSelect>,
655
+ get: Procedure<string | { id: string, ...extra }, TSelect>,
511
656
  create: Procedure<TInsert, TSelect>,
512
657
  update: Procedure<{ id: string, data: TUpdate }, TSelect>,
513
658
  delete: Procedure<string, TSelect>,
@@ -525,18 +670,18 @@ const ordersRouter = createCrudRouter({
525
670
  ### 使用 procedure 配置
526
671
 
527
672
  ```typescript
528
- import { createCrudRouter } from "@wordrhyme/auto-crud-server";
529
- import { protectedProcedure, adminProcedure, publicProcedure } from "../trpc";
673
+ import { createCrudRouter } from '@wordrhyme/auto-crud-server';
674
+ import { protectedProcedure, adminProcedure, publicProcedure } from '../trpc';
530
675
 
531
676
  export const tasksRouter = createCrudRouter({
532
677
  table: tasks,
533
678
  // 按操作指定不同的 procedure
534
679
  procedure: {
535
- list: publicProcedure, // 公开读
680
+ list: publicProcedure, // 公开读
536
681
  get: publicProcedure,
537
682
  create: protectedProcedure, // 需要登录
538
683
  update: protectedProcedure,
539
- delete: adminProcedure, // 需要管理员
684
+ delete: adminProcedure, // 需要管理员
540
685
  default: protectedProcedure,
541
686
  },
542
687
  });
@@ -549,8 +694,8 @@ export const tasksRouter = createCrudRouter({
549
694
  table: tasks,
550
695
  guard: (ctx, operation) => {
551
696
  // 删除操作需要管理员权限
552
- if (operation === "delete") {
553
- return ctx.user.role === "admin";
697
+ if (operation === 'delete') {
698
+ return ctx.user.role === 'admin';
554
699
  }
555
700
  // 其他操作需要登录
556
701
  return !!ctx.user;
@@ -561,7 +706,7 @@ export const tasksRouter = createCrudRouter({
561
706
  ### 使用 scope(行级过滤 RLS)
562
707
 
563
708
  ```typescript
564
- import { eq } from "drizzle-orm";
709
+ import { eq } from 'drizzle-orm';
565
710
 
566
711
  export const tasksRouter = createCrudRouter({
567
712
  table: tasks,
@@ -592,7 +737,7 @@ export const tasksRouter = createCrudRouter({
592
737
  // 写入时强制覆盖字段(防止伪造)
593
738
  inject: (ctx, operation) => ({
594
739
  tenantId: ctx.user.tenantId,
595
- ...(operation === "create" ? { createdBy: ctx.user.id } : { updatedBy: ctx.user.id }),
740
+ ...(operation === 'create' ? { createdBy: ctx.user.id } : { updatedBy: ctx.user.id }),
596
741
  }),
597
742
  });
598
743
  ```
@@ -606,9 +751,9 @@ export const tasksRouter = createCrudRouter({
606
751
  使用 `.procedures` 属性 spread 出 CRUD 路由,然后添加自定义路由:
607
752
 
608
753
  ```typescript
609
- import { createCrudRouter, router } from "@wordrhyme/auto-crud-server";
610
- import { protectedProcedure } from "../trpc";
611
- import { z } from "zod";
754
+ import { createCrudRouter, router } from '@wordrhyme/auto-crud-server';
755
+ import { protectedProcedure } from '../trpc';
756
+ import { z } from 'zod';
612
757
 
613
758
  // 创建基础 CRUD
614
759
  const tasksCrud = createCrudRouter({
@@ -623,20 +768,14 @@ export const tasksRouter = router({
623
768
  archive: protectedProcedure
624
769
  .input(z.object({ id: z.string() }))
625
770
  .mutation(async ({ input, ctx }) => {
626
- return ctx.db
627
- .update(tasks)
628
- .set({ archived: true })
629
- .where(eq(tasks.id, input.id));
771
+ return ctx.db.update(tasks).set({ archived: true }).where(eq(tasks.id, input.id));
630
772
  }),
631
773
 
632
774
  // 自定义路由:取消归档
633
775
  unarchive: protectedProcedure
634
776
  .input(z.object({ id: z.string() }))
635
777
  .mutation(async ({ input, ctx }) => {
636
- return ctx.db
637
- .update(tasks)
638
- .set({ archived: false })
639
- .where(eq(tasks.id, input.id));
778
+ return ctx.db.update(tasks).set({ archived: false }).where(eq(tasks.id, input.id));
640
779
  }),
641
780
  });
642
781
  ```
@@ -659,27 +798,21 @@ export const appRouter = router({
659
798
  ### 自定义过滤逻辑
660
799
 
661
800
  ```typescript
662
- // 覆盖 list 路由,添加自定义过滤逻辑
663
- const tasksCrud = createCrudRouter({ table: tasks });
664
-
665
- export const tasksRouter = router({
666
- ...tasksCrud.procedures,
667
-
668
- // 覆盖 list,添加自定义逻辑
669
- list: protectedProcedure
670
- .input(listInputSchema)
671
- .query(async ({ input, ctx }) => {
672
- // 自定义过滤逻辑
673
- const customFilters = input.filters?.map(filter => {
674
- if (filter.id === "customField") {
675
- return { ...filter, operator: "custom" };
801
+ // 通过 middleware 调整输入,继续复用内置分页/排序/过滤/count/scope/guard
802
+ const tasksCrud = createCrudRouter({
803
+ table: tasks,
804
+ middleware: {
805
+ list: async ({ input, next }) => {
806
+ const filters = input.filters?.map((filter) => {
807
+ if (filter.id === 'customField') {
808
+ return { ...filter, operator: 'custom' };
676
809
  }
677
810
  return filter;
678
811
  });
679
812
 
680
- // 调用原始 list(需要手动实现或使用 db 查询)
681
- return ctx.db.select().from(tasks).where(...);
682
- }),
813
+ return next({ ...input, filters });
814
+ },
815
+ },
683
816
  });
684
817
  ```
685
818
 
@@ -692,7 +825,7 @@ export const tasksRouter = router({
692
825
  ### 完整控制模式
693
826
 
694
827
  ```typescript
695
- import { createCrudRouter } from "@wordrhyme/auto-crud-server";
828
+ import { createCrudRouter } from '@wordrhyme/auto-crud-server';
696
829
 
697
830
  const tasksRouter = createCrudRouter({
698
831
  table: tasks,
@@ -709,7 +842,7 @@ const tasksRouter = createCrudRouter({
709
842
 
710
843
  // 3. 执行副作用
711
844
  await sendNotification(result);
712
- await logAudit(ctx.user, "create", result);
845
+ await logAudit(ctx.user, 'create', result);
713
846
 
714
847
  // 4. 返回结果(可修改)
715
848
  return result;
@@ -719,15 +852,15 @@ const tasksRouter = createCrudRouter({
719
852
  update: async ({ ctx, id, data, existing, next }) => {
720
853
  // 检查权限
721
854
  if (existing.ownerId !== ctx.user.id) {
722
- throw new Error("Forbidden");
855
+ throw new Error('Forbidden');
723
856
  }
724
857
  return next(data);
725
858
  },
726
859
 
727
860
  // 删除:条件拦截
728
861
  delete: async ({ ctx, id, existing, next }) => {
729
- if (existing.status === "locked") {
730
- throw new Error("Cannot delete locked resource");
862
+ if (existing.status === 'locked') {
863
+ throw new Error('Cannot delete locked resource');
731
864
  }
732
865
  return next();
733
866
  },
@@ -746,7 +879,7 @@ import {
746
879
  beforeMiddleware,
747
880
  afterCreate,
748
881
  beforeCreate,
749
- } from "@wordrhyme/auto-crud-server";
882
+ } from '@wordrhyme/auto-crud-server';
750
883
 
751
884
  const tasksRouter = createCrudRouter({
752
885
  table: tasks,
@@ -755,7 +888,7 @@ const tasksRouter = createCrudRouter({
755
888
  // 简单副作用:只在操作后执行
756
889
  create: afterMiddleware(async (ctx, result) => {
757
890
  await sendEmail(result);
758
- await logAudit(ctx.user, "create", result);
891
+ await logAudit(ctx.user, 'create', result);
759
892
  }),
760
893
 
761
894
  // 修改输入:只在操作前执行
@@ -773,15 +906,15 @@ const tasksRouter = createCrudRouter({
773
906
 
774
907
  ### 可用的工具函数
775
908
 
776
- | 函数 | 用途 | 示例 |
777
- |------|------|------|
778
- | `afterMiddleware(fn)` | 操作后执行副作用 | 日志、通知、审计 |
779
- | `afterMiddlewareTransform(fn)` | 操作后修改返回值 | 添加计算字段 |
780
- | `beforeMiddleware(fn)` | 操作前修改输入 | 注入用户ID、生成slug |
781
- | `composeMiddleware(...fns)` | 组合多个中间件 | 复杂场景 |
782
- | `afterList`, `beforeList` | list 操作专用 | 分页后处理 |
783
- | `afterCreate`, `beforeCreate` | create 操作专用 | 创建通知 |
784
- | `afterUpdate`, `afterDelete` | update/delete 专用 | 更新/删除通知 |
909
+ | 函数 | 用途 | 示例 |
910
+ | ------------------------------ | ------------------ | -------------------- |
911
+ | `afterMiddleware(fn)` | 操作后执行副作用 | 日志、通知、审计 |
912
+ | `afterMiddlewareTransform(fn)` | 操作后修改返回值 | 添加计算字段 |
913
+ | `beforeMiddleware(fn)` | 操作前修改输入 | 注入用户ID、生成slug |
914
+ | `composeMiddleware(...fns)` | 组合多个中间件 | 复杂场景 |
915
+ | `afterList`, `beforeList` | list 操作专用 | 分页后处理 |
916
+ | `afterCreate`, `beforeCreate` | create 操作专用 | 创建通知 |
917
+ | `afterUpdate`, `afterDelete` | update/delete 专用 | 更新/删除通知 |
785
918
 
786
919
  ---
787
920
 
@@ -790,9 +923,9 @@ const tasksRouter = createCrudRouter({
790
923
  ### 与 Drizzle ORM 集成
791
924
 
792
925
  ```typescript
793
- import { drizzle } from "drizzle-orm/postgres-js";
794
- import postgres from "postgres";
795
- import * as schema from "./schema";
926
+ import { drizzle } from 'drizzle-orm/postgres-js';
927
+ import postgres from 'postgres';
928
+ import * as schema from './schema';
796
929
 
797
930
  const client = postgres(process.env.DATABASE_URL!);
798
931
  export const db = drizzle(client, { schema });
@@ -805,12 +938,12 @@ export const createContext = () => ({ db });
805
938
 
806
939
  ```typescript
807
940
  // app/api/trpc/[trpc]/route.ts
808
- import { fetchRequestHandler } from "@trpc/server/adapters/fetch";
809
- import { appRouter } from "@/server/routers";
941
+ import { fetchRequestHandler } from '@trpc/server/adapters/fetch';
942
+ import { appRouter } from '@/server/routers';
810
943
 
811
944
  const handler = (req: Request) =>
812
945
  fetchRequestHandler({
813
- endpoint: "/api/trpc",
946
+ endpoint: '/api/trpc',
814
947
  req,
815
948
  router: appRouter,
816
949
  createContext: () => ({ db }),
@@ -822,18 +955,18 @@ export { handler as GET, handler as POST };
822
955
  ### 与 Express 集成
823
956
 
824
957
  ```typescript
825
- import express from "express";
826
- import { createExpressMiddleware } from "@trpc/server/adapters/express";
827
- import { appRouter } from "./server/routers";
958
+ import express from 'express';
959
+ import { createExpressMiddleware } from '@trpc/server/adapters/express';
960
+ import { appRouter } from './server/routers';
828
961
 
829
962
  const app = express();
830
963
 
831
964
  app.use(
832
- "/trpc",
965
+ '/trpc',
833
966
  createExpressMiddleware({
834
967
  router: appRouter,
835
968
  createContext: () => ({ db }),
836
- })
969
+ }),
837
970
  );
838
971
 
839
972
  app.listen(3000);
@@ -848,7 +981,7 @@ app.listen(3000);
848
981
  ```typescript
849
982
  export const usersRouter = createCrudRouter({
850
983
  table: users,
851
- idField: "userId", // 使用自定义 ID 字段
984
+ idField: 'userId', // 使用自定义 ID 字段
852
985
  });
853
986
  ```
854
987
 
@@ -858,7 +991,7 @@ export const usersRouter = createCrudRouter({
858
991
  export const ordersRouter = createCrudRouter({
859
992
  table: orders,
860
993
  // 自动派生 schema 时排除这些字段
861
- omitFields: ["id", "createdAt", "updatedAt", "internalCode"],
994
+ omitFields: ['id', 'createdAt', 'updatedAt', 'internalCode'],
862
995
  });
863
996
  ```
864
997
 
@@ -871,10 +1004,10 @@ export const tasksRouter = createCrudRouter({
871
1004
  softDelete: true,
872
1005
 
873
1006
  // 方式 2:指定列名
874
- softDelete: "deletedAt",
1007
+ softDelete: 'deletedAt',
875
1008
 
876
1009
  // 方式 3:完整配置(用于布尔字段)
877
- softDelete: { column: "isDeleted", value: () => true },
1010
+ softDelete: { column: 'isDeleted', value: () => true },
878
1011
  });
879
1012
  ```
880
1013
 
@@ -883,7 +1016,7 @@ export const tasksRouter = createCrudRouter({
883
1016
  ```typescript
884
1017
  export const tasksRouter = createCrudRouter({
885
1018
  table: tasks,
886
- maxBatchSize: 50, // 默认 100
1019
+ maxBatchSize: 50, // 默认 100
887
1020
  });
888
1021
  ```
889
1022
 
@@ -892,8 +1025,8 @@ export const tasksRouter = createCrudRouter({
892
1025
  ```typescript
893
1026
  export const tasksRouter = createCrudRouter({
894
1027
  table: tasks,
895
- filterableColumns: ["title", "status", "priority"], // 只允许这些列过滤
896
- sortableColumns: ["title", "createdAt", "priority"], // 只允许这些列排序
1028
+ filterableColumns: ['title', 'status', 'priority'], // 只允许这些列过滤
1029
+ sortableColumns: ['title', 'createdAt', 'priority'], // 只允许这些列排序
897
1030
  });
898
1031
  ```
899
1032
 
@@ -903,13 +1036,21 @@ export const tasksRouter = createCrudRouter({
903
1036
 
904
1037
  ```typescript
905
1038
  // 主要导出
906
- export { createCrudRouter } from "./routers/_factory";
1039
+ export {
1040
+ baseExportInputSchema,
1041
+ baseGetInputSchema,
1042
+ baseListInputSchema,
1043
+ createCrudRouter,
1044
+ } from './routers/_factory';
907
1045
  export type {
908
1046
  CrudRouterConfig,
909
1047
  CrudMiddleware,
1048
+ GetInput,
910
1049
  ListInput,
911
1050
  ListResult,
912
- } from "./types/config";
1051
+ ExportInput,
1052
+ ExportResult,
1053
+ } from './types/config';
913
1054
 
914
1055
  // Middleware 工具函数
915
1056
  export {
@@ -923,14 +1064,14 @@ export {
923
1064
  beforeCreate,
924
1065
  afterUpdate,
925
1066
  afterDelete,
926
- } from "./lib/middleware-helpers";
1067
+ } from './lib/middleware-helpers';
927
1068
 
928
1069
  // tRPC 工具
929
- export { router, publicProcedure } from "./trpc";
1070
+ export { router, publicProcedure } from './trpc';
930
1071
 
931
1072
  // 示例路由(可选)
932
- export { appRouter } from "./routers";
933
- export type { AppRouter } from "./routers";
1073
+ export { appRouter } from './routers';
1074
+ export type { AppRouter } from './routers';
934
1075
  ```
935
1076
 
936
1077
  ---
@@ -942,6 +1083,7 @@ export type { AppRouter } from "./routers";
942
1083
  **错误**: `Cannot find module '@wordrhyme/auto-crud-server'`
943
1084
 
944
1085
  **解决方案**:
1086
+
945
1087
  ```bash
946
1088
  pnpm install @wordrhyme/auto-crud-server
947
1089
  ```
@@ -951,6 +1093,7 @@ pnpm install @wordrhyme/auto-crud-server
951
1093
  **错误**: `Type 'X' is not assignable to type 'Y'`
952
1094
 
953
1095
  **解决方案**: 确保 Zod 版本一致
1096
+
954
1097
  ```bash
955
1098
  pnpm list zod
956
1099
  # 确保所有包使用相同的 Zod 版本
@@ -961,9 +1104,10 @@ pnpm list zod
961
1104
  **错误**: `connect ECONNREFUSED`
962
1105
 
963
1106
  **解决方案**: 检查数据库连接字符串
1107
+
964
1108
  ```typescript
965
1109
  // .env
966
- DATABASE_URL="postgresql://user:password@localhost:5432/dbname"
1110
+ DATABASE_URL = 'postgresql://user:password@localhost:5432/dbname';
967
1111
  ```
968
1112
 
969
1113
  ---