@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 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
+ [![npm version](https://img.shields.io/npm/v/@wordrhyme/auto-crud-server.svg)](https://www.npmjs.com/package/@wordrhyme/auto-crud-server)
8
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](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