@wordrhyme/auto-crud-server 0.7.0 → 0.9.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/README.md +161 -95
- package/dist/index.cjs +59 -11
- package/dist/index.d.cts +65 -17
- package/dist/index.d.ts +64 -16
- package/dist/index.js +58 -11
- package/package.json +3 -3
package/README.md
CHANGED
|
@@ -75,27 +75,32 @@ export const tasks = pgTable("tasks", {
|
|
|
75
75
|
// src/server/routers/tasks.ts
|
|
76
76
|
import { createCrudRouter } from "@wordrhyme/auto-crud-server";
|
|
77
77
|
import { tasks } from "@/db/schema";
|
|
78
|
+
|
|
79
|
+
// 🚀 零配置!一行代码生成完整 CRUD 路由
|
|
80
|
+
export const tasksRouter = createCrudRouter({
|
|
81
|
+
table: tasks,
|
|
82
|
+
// Schema 自动从 table 派生,排除 id, createdAt, updatedAt
|
|
83
|
+
});
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
**或者,显式传入 Schema:**
|
|
87
|
+
|
|
88
|
+
```typescript
|
|
89
|
+
import { createCrudRouter } from "@wordrhyme/auto-crud-server";
|
|
90
|
+
import { tasks } from "@/db/schema";
|
|
78
91
|
import { createSelectSchema } from "drizzle-zod";
|
|
79
92
|
|
|
80
93
|
// 从 Drizzle Schema 自动生成 Zod Schema
|
|
81
|
-
const
|
|
82
|
-
|
|
83
|
-
// 创建输入 Schema
|
|
84
|
-
const insertTaskSchema = selectTaskSchema.omit({
|
|
94
|
+
const taskSchema = createSelectSchema(tasks).omit({
|
|
85
95
|
id: true,
|
|
86
96
|
createdAt: true,
|
|
87
97
|
updatedAt: true,
|
|
88
98
|
});
|
|
89
99
|
|
|
90
|
-
const updateTaskSchema = insertTaskSchema.partial();
|
|
91
|
-
|
|
92
|
-
// 🚀 一行代码生成完整 CRUD 路由
|
|
93
100
|
export const tasksRouter = createCrudRouter({
|
|
94
101
|
table: tasks,
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
updateSchema: updateTaskSchema,
|
|
98
|
-
idField: "id", // 可选,默认为 "id"
|
|
102
|
+
schema: taskSchema, // 主 Schema(用于 create/upsert)
|
|
103
|
+
// updateSchema 自动派生为 schema.partial()
|
|
99
104
|
});
|
|
100
105
|
```
|
|
101
106
|
|
|
@@ -440,14 +445,57 @@ await trpc.tasks.list({
|
|
|
440
445
|
|
|
441
446
|
```typescript
|
|
442
447
|
interface CrudRouterConfig<TTable, TSelect, TInsert, TUpdate> {
|
|
448
|
+
// ========== 必填 ==========
|
|
443
449
|
table: TTable; // Drizzle 表定义
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
450
|
+
|
|
451
|
+
// ========== Schema 配置(可选) ==========
|
|
452
|
+
schema?: z.ZodType<TInsert>; // 主 Schema(用于 create/upsert)
|
|
453
|
+
updateSchema?: z.ZodType<TUpdate>;// 更新 Schema(覆盖自动派生)
|
|
454
|
+
selectSchema?: z.ZodType<TSelect>;// 查询返回 Schema
|
|
455
|
+
|
|
456
|
+
// ========== 其他配置 ==========
|
|
447
457
|
idField?: string; // ID 字段名,默认 "id"
|
|
458
|
+
omitFields?: string[]; // 自动派生时排除的字段
|
|
459
|
+
// 默认 ["id", "createdAt", "updatedAt"]
|
|
448
460
|
}
|
|
449
461
|
```
|
|
450
462
|
|
|
463
|
+
#### Schema 派生规则
|
|
464
|
+
|
|
465
|
+
| 配置 | 派生行为 |
|
|
466
|
+
|-----|---------|
|
|
467
|
+
| 无 `schema` | 从 `table` 自动派生,排除 `omitFields` |
|
|
468
|
+
| 无 `updateSchema` | 从 `schema.partial().refine(nonEmpty)` 派生 |
|
|
469
|
+
| 无 `selectSchema` | 若有 `schema` 则使用它,否则从 `table` 派生 |
|
|
470
|
+
|
|
471
|
+
#### 使用示例
|
|
472
|
+
|
|
473
|
+
```typescript
|
|
474
|
+
// 1. 零配置 - 全部自动派生
|
|
475
|
+
const tasksRouter = createCrudRouter({
|
|
476
|
+
table: tasks,
|
|
477
|
+
});
|
|
478
|
+
|
|
479
|
+
// 2. 传入 schema,updateSchema 自动派生
|
|
480
|
+
const postsRouter = createCrudRouter({
|
|
481
|
+
table: posts,
|
|
482
|
+
schema: postSchema,
|
|
483
|
+
});
|
|
484
|
+
|
|
485
|
+
// 3. 传入 schema + 自定义 updateSchema
|
|
486
|
+
const usersRouter = createCrudRouter({
|
|
487
|
+
table: users,
|
|
488
|
+
schema: userSchema,
|
|
489
|
+
updateSchema: customUpdateSchema,
|
|
490
|
+
});
|
|
491
|
+
|
|
492
|
+
// 4. 自定义排除字段
|
|
493
|
+
const ordersRouter = createCrudRouter({
|
|
494
|
+
table: orders,
|
|
495
|
+
omitFields: ["id", "createdAt", "updatedAt", "internalCode"],
|
|
496
|
+
});
|
|
497
|
+
```
|
|
498
|
+
|
|
451
499
|
#### 返回值
|
|
452
500
|
|
|
453
501
|
```typescript
|
|
@@ -459,95 +507,94 @@ interface CrudRouterConfig<TTable, TSelect, TInsert, TUpdate> {
|
|
|
459
507
|
// 可 spread 的 procedures 对象(用于扩展自定义路由)
|
|
460
508
|
procedures: {
|
|
461
509
|
list: Procedure<ListInput, ListOutput>,
|
|
462
|
-
|
|
510
|
+
get: Procedure<string, TSelect>,
|
|
463
511
|
create: Procedure<TInsert, TSelect>,
|
|
464
512
|
update: Procedure<{ id: string, data: TUpdate }, TSelect>,
|
|
465
513
|
delete: Procedure<string, TSelect>,
|
|
466
514
|
deleteMany: Procedure<string[], { deleted: number }>,
|
|
467
515
|
updateMany: Procedure<{ ids: string[], data: TUpdate }, { updated: number }>,
|
|
516
|
+
upsert: Procedure<TInsert, { data: TSelect, isNew: boolean }>,
|
|
468
517
|
}
|
|
469
518
|
}
|
|
470
519
|
```
|
|
471
520
|
|
|
472
|
-
|
|
521
|
+
---
|
|
522
|
+
|
|
523
|
+
## 🔐 权限控制
|
|
524
|
+
|
|
525
|
+
### 使用 procedure 配置
|
|
473
526
|
|
|
474
527
|
```typescript
|
|
475
528
|
import { createCrudRouter } from "@wordrhyme/auto-crud-server";
|
|
476
|
-
import {
|
|
477
|
-
import { createSelectSchema } from "drizzle-zod";
|
|
478
|
-
import { z } from "zod";
|
|
529
|
+
import { protectedProcedure, adminProcedure, publicProcedure } from "../trpc";
|
|
479
530
|
|
|
480
|
-
const
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
531
|
+
export const tasksRouter = createCrudRouter({
|
|
532
|
+
table: tasks,
|
|
533
|
+
// 按操作指定不同的 procedure
|
|
534
|
+
procedure: {
|
|
535
|
+
list: publicProcedure, // 公开读
|
|
536
|
+
get: publicProcedure,
|
|
537
|
+
create: protectedProcedure, // 需要登录
|
|
538
|
+
update: protectedProcedure,
|
|
539
|
+
delete: adminProcedure, // 需要管理员
|
|
540
|
+
default: protectedProcedure,
|
|
541
|
+
},
|
|
486
542
|
});
|
|
543
|
+
```
|
|
487
544
|
|
|
488
|
-
|
|
545
|
+
### 使用 guard(操作级守卫)
|
|
489
546
|
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
547
|
+
```typescript
|
|
548
|
+
export const tasksRouter = createCrudRouter({
|
|
549
|
+
table: tasks,
|
|
550
|
+
guard: (ctx, operation) => {
|
|
551
|
+
// 删除操作需要管理员权限
|
|
552
|
+
if (operation === "delete") {
|
|
553
|
+
return ctx.user.role === "admin";
|
|
554
|
+
}
|
|
555
|
+
// 其他操作需要登录
|
|
556
|
+
return !!ctx.user;
|
|
557
|
+
},
|
|
496
558
|
});
|
|
497
559
|
```
|
|
498
560
|
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
## 🔐 权限控制
|
|
502
|
-
|
|
503
|
-
### 使用 tRPC Middleware
|
|
561
|
+
### 使用 scope(行级过滤 RLS)
|
|
504
562
|
|
|
505
563
|
```typescript
|
|
506
|
-
import {
|
|
507
|
-
|
|
508
|
-
const t = initTRPC.context<Context>().create();
|
|
564
|
+
import { eq } from "drizzle-orm";
|
|
509
565
|
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
566
|
+
export const tasksRouter = createCrudRouter({
|
|
567
|
+
table: tasks,
|
|
568
|
+
// 自动注入到所有查询的 WHERE 子句
|
|
569
|
+
scope: (ctx, table, operation) => {
|
|
570
|
+
return eq(table.tenantId, ctx.user.tenantId);
|
|
571
|
+
},
|
|
516
572
|
});
|
|
573
|
+
```
|
|
574
|
+
|
|
575
|
+
### 使用 authorize(资源级检查 ABAC)
|
|
517
576
|
|
|
518
|
-
|
|
577
|
+
```typescript
|
|
519
578
|
export const tasksRouter = createCrudRouter({
|
|
520
579
|
table: tasks,
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
// 需要在路由层面添加权限控制
|
|
580
|
+
// 在 get/update/delete 时,预取资源后调用
|
|
581
|
+
authorize: (ctx, resource, operation) => {
|
|
582
|
+
return resource.ownerId === ctx.user.id;
|
|
583
|
+
},
|
|
526
584
|
});
|
|
527
|
-
|
|
528
|
-
// 包装路由添加权限
|
|
529
|
-
export const protectedTasksRouter = {
|
|
530
|
-
list: protectedProcedure.query(async ({ input, ctx }) => {
|
|
531
|
-
return tasksRouter.list(input, ctx);
|
|
532
|
-
}),
|
|
533
|
-
// ... 其他路由
|
|
534
|
-
};
|
|
535
585
|
```
|
|
536
586
|
|
|
537
|
-
###
|
|
587
|
+
### 使用 inject(强制注入字段)
|
|
538
588
|
|
|
539
589
|
```typescript
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
.select()
|
|
549
|
-
.from(tasks)
|
|
550
|
-
.where(eq(tasks.userId, ctx.user.id)); // 只查询当前用户的任务
|
|
590
|
+
export const tasksRouter = createCrudRouter({
|
|
591
|
+
table: tasks,
|
|
592
|
+
// 写入时强制覆盖字段(防止伪造)
|
|
593
|
+
inject: (ctx, operation) => ({
|
|
594
|
+
tenantId: ctx.user.tenantId,
|
|
595
|
+
...(operation === "create" ? { createdBy: ctx.user.id } : { updatedBy: ctx.user.id }),
|
|
596
|
+
}),
|
|
597
|
+
});
|
|
551
598
|
```
|
|
552
599
|
|
|
553
600
|
---
|
|
@@ -566,9 +613,6 @@ import { z } from "zod";
|
|
|
566
613
|
// 创建基础 CRUD
|
|
567
614
|
const tasksCrud = createCrudRouter({
|
|
568
615
|
table: tasks,
|
|
569
|
-
selectSchema: selectTaskSchema,
|
|
570
|
-
insertSchema: insertTaskSchema,
|
|
571
|
-
updateSchema: updateTaskSchema,
|
|
572
616
|
});
|
|
573
617
|
|
|
574
618
|
// 使用 .procedures spread 并添加自定义路由
|
|
@@ -604,9 +648,6 @@ export const tasksRouter = router({
|
|
|
604
648
|
```typescript
|
|
605
649
|
const tasksCrud = createCrudRouter({
|
|
606
650
|
table: tasks,
|
|
607
|
-
selectSchema: selectTaskSchema,
|
|
608
|
-
insertSchema: insertTaskSchema,
|
|
609
|
-
updateSchema: updateTaskSchema,
|
|
610
651
|
});
|
|
611
652
|
|
|
612
653
|
// 直接嵌套到 appRouter
|
|
@@ -619,7 +660,7 @@ export const appRouter = router({
|
|
|
619
660
|
|
|
620
661
|
```typescript
|
|
621
662
|
// 覆盖 list 路由,添加自定义过滤逻辑
|
|
622
|
-
const tasksCrud = createCrudRouter({
|
|
663
|
+
const tasksCrud = createCrudRouter({ table: tasks });
|
|
623
664
|
|
|
624
665
|
export const tasksRouter = router({
|
|
625
666
|
...tasksCrud.procedures,
|
|
@@ -655,8 +696,6 @@ import { createCrudRouter } from "@wordrhyme/auto-crud-server";
|
|
|
655
696
|
|
|
656
697
|
const tasksRouter = createCrudRouter({
|
|
657
698
|
table: tasks,
|
|
658
|
-
insertSchema: insertTaskSchema,
|
|
659
|
-
updateSchema: updateTaskSchema,
|
|
660
699
|
|
|
661
700
|
// 中间件:完全控制操作流程
|
|
662
701
|
middleware: {
|
|
@@ -711,8 +750,6 @@ import {
|
|
|
711
750
|
|
|
712
751
|
const tasksRouter = createCrudRouter({
|
|
713
752
|
table: tasks,
|
|
714
|
-
insertSchema: insertTaskSchema,
|
|
715
|
-
updateSchema: updateTaskSchema,
|
|
716
753
|
|
|
717
754
|
middleware: {
|
|
718
755
|
// 简单副作用:只在操作后执行
|
|
@@ -811,24 +848,53 @@ app.listen(3000);
|
|
|
811
848
|
```typescript
|
|
812
849
|
export const usersRouter = createCrudRouter({
|
|
813
850
|
table: users,
|
|
814
|
-
selectSchema: selectUserSchema,
|
|
815
|
-
insertSchema: insertUserSchema,
|
|
816
|
-
updateSchema: updateUserSchema,
|
|
817
851
|
idField: "userId", // 使用自定义 ID 字段
|
|
818
852
|
});
|
|
819
853
|
```
|
|
820
854
|
|
|
821
|
-
###
|
|
855
|
+
### 自定义排除字段
|
|
822
856
|
|
|
823
857
|
```typescript
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
858
|
+
export const ordersRouter = createCrudRouter({
|
|
859
|
+
table: orders,
|
|
860
|
+
// 自动派生 schema 时排除这些字段
|
|
861
|
+
omitFields: ["id", "createdAt", "updatedAt", "internalCode"],
|
|
862
|
+
});
|
|
863
|
+
```
|
|
864
|
+
|
|
865
|
+
### 软删除
|
|
866
|
+
|
|
867
|
+
```typescript
|
|
868
|
+
export const tasksRouter = createCrudRouter({
|
|
869
|
+
table: tasks,
|
|
870
|
+
// 方式 1:使用默认列名 'deletedAt'
|
|
871
|
+
softDelete: true,
|
|
872
|
+
|
|
873
|
+
// 方式 2:指定列名
|
|
874
|
+
softDelete: "deletedAt",
|
|
875
|
+
|
|
876
|
+
// 方式 3:完整配置(用于布尔字段)
|
|
877
|
+
softDelete: { column: "isDeleted", value: () => true },
|
|
878
|
+
});
|
|
879
|
+
```
|
|
880
|
+
|
|
881
|
+
### 批量操作限制
|
|
882
|
+
|
|
883
|
+
```typescript
|
|
884
|
+
export const tasksRouter = createCrudRouter({
|
|
885
|
+
table: tasks,
|
|
886
|
+
maxBatchSize: 50, // 默认 100
|
|
887
|
+
});
|
|
888
|
+
```
|
|
889
|
+
|
|
890
|
+
### 列白名单
|
|
891
|
+
|
|
892
|
+
```typescript
|
|
893
|
+
export const tasksRouter = createCrudRouter({
|
|
894
|
+
table: tasks,
|
|
895
|
+
filterableColumns: ["title", "status", "priority"], // 只允许这些列过滤
|
|
896
|
+
sortableColumns: ["title", "createdAt", "priority"], // 只允许这些列排序
|
|
897
|
+
});
|
|
832
898
|
```
|
|
833
899
|
|
|
834
900
|
---
|
package/dist/index.cjs
CHANGED
|
@@ -25,6 +25,8 @@ let zod = require("zod");
|
|
|
25
25
|
zod = __toESM(zod);
|
|
26
26
|
let drizzle_orm = require("drizzle-orm");
|
|
27
27
|
drizzle_orm = __toESM(drizzle_orm);
|
|
28
|
+
let drizzle_zod = require("drizzle-zod");
|
|
29
|
+
drizzle_zod = __toESM(drizzle_zod);
|
|
28
30
|
let __trpc_server = require("@trpc/server");
|
|
29
31
|
__trpc_server = __toESM(__trpc_server);
|
|
30
32
|
let superjson = require("superjson");
|
|
@@ -399,6 +401,26 @@ const dataTableConfig = {
|
|
|
399
401
|
joinOperators: ["and", "or"]
|
|
400
402
|
};
|
|
401
403
|
|
|
404
|
+
//#endregion
|
|
405
|
+
//#region src/lib/schema-utils.ts
|
|
406
|
+
/**
|
|
407
|
+
* 检查是否为 ZodObject 类型
|
|
408
|
+
* 兼容 Zod v3 和 v4
|
|
409
|
+
*
|
|
410
|
+
* 实现策略:检查 schema 是否具有 ZodObject 特有的 .shape 属性
|
|
411
|
+
* 这种方式比检查内部 _def 属性更稳定,因为不依赖 Zod 的内部实现细节
|
|
412
|
+
*/
|
|
413
|
+
function isZodObject(schema) {
|
|
414
|
+
return schema !== null && typeof schema === "object" && "shape" in schema && typeof schema.shape === "object" && schema.shape !== null;
|
|
415
|
+
}
|
|
416
|
+
/**
|
|
417
|
+
* 非空对象验证器
|
|
418
|
+
* 用于 updateSchema 的 refine,防止空更新
|
|
419
|
+
*/
|
|
420
|
+
function nonEmpty(value) {
|
|
421
|
+
return Object.keys(value).length > 0;
|
|
422
|
+
}
|
|
423
|
+
|
|
402
424
|
//#endregion
|
|
403
425
|
//#region src/routers/_factory.ts
|
|
404
426
|
function resolveSoftDelete(option) {
|
|
@@ -485,20 +507,46 @@ const listInputSchema = zod.z.object({
|
|
|
485
507
|
filters: zod.z.array(filterItemSchema).optional(),
|
|
486
508
|
joinOperator: zod.z.enum(["and", "or"]).default("and")
|
|
487
509
|
});
|
|
510
|
+
const DEFAULT_OMIT_FIELDS = [
|
|
511
|
+
"id",
|
|
512
|
+
"createdAt",
|
|
513
|
+
"updatedAt"
|
|
514
|
+
];
|
|
515
|
+
/**
|
|
516
|
+
* 解析并派生 Schema
|
|
517
|
+
* - 优先使用显式传入的 Schema
|
|
518
|
+
* - 否则从 Drizzle table 自动派生
|
|
519
|
+
*/
|
|
520
|
+
function resolveSchemas(config) {
|
|
521
|
+
const { table, omitFields = DEFAULT_OMIT_FIELDS } = config;
|
|
522
|
+
let schema;
|
|
523
|
+
if (config.schema) {
|
|
524
|
+
if (!isZodObject(config.schema)) throw new Error("[createCrudRouter] schema must be a ZodObject (not ZodEffects or other types). If you're using .refine() or .transform(), please remove them from schema.");
|
|
525
|
+
schema = config.schema;
|
|
526
|
+
} else {
|
|
527
|
+
const rawSchema = (0, drizzle_zod.createInsertSchema)(table);
|
|
528
|
+
if (!isZodObject(rawSchema)) throw new Error("[createCrudRouter] drizzle-zod returned non-ZodObject schema. This may be due to table refinements. Please provide schema explicitly.");
|
|
529
|
+
const omitConfig = Object.fromEntries(omitFields.map((f) => [f, true]));
|
|
530
|
+
schema = rawSchema.omit(omitConfig);
|
|
531
|
+
}
|
|
532
|
+
let selectSchema;
|
|
533
|
+
if (config.selectSchema) selectSchema = config.selectSchema;
|
|
534
|
+
else if (config.schema) selectSchema = schema;
|
|
535
|
+
else selectSchema = (0, drizzle_zod.createSelectSchema)(table);
|
|
536
|
+
const updateSchema = config.updateSchema ?? schema.partial().refine(nonEmpty, { message: "Update payload cannot be empty. At least one field is required." });
|
|
537
|
+
return {
|
|
538
|
+
selectSchema,
|
|
539
|
+
schema,
|
|
540
|
+
updateSchema
|
|
541
|
+
};
|
|
542
|
+
}
|
|
488
543
|
function validateColumn(columnId, allowedColumns) {
|
|
489
544
|
if (!allowedColumns || allowedColumns.length === 0) return true;
|
|
490
545
|
return allowedColumns.includes(columnId);
|
|
491
546
|
}
|
|
492
|
-
/**
|
|
493
|
-
* 创建 CRUD Router
|
|
494
|
-
*
|
|
495
|
-
* @typeParam TContext - 上下文类型(包含 db)
|
|
496
|
-
* @typeParam TSelect - 查询返回类型(从 selectSchema 或 insertSchema 推断)
|
|
497
|
-
* @typeParam TInsert - 创建输入类型(从 insertSchema 推断)
|
|
498
|
-
* @typeParam TUpdate - 更新输入类型(从 updateSchema 推断)
|
|
499
|
-
*/
|
|
500
547
|
function createCrudRouter(config) {
|
|
501
|
-
const { table,
|
|
548
|
+
const { table, idField = "id", filterableColumns, sortableColumns, maxBatchSize = 100, softDelete: softDeleteOption, middleware = {} } = config;
|
|
549
|
+
const { schema, updateSchema } = resolveSchemas(config);
|
|
502
550
|
const resolved = resolveConfig(config);
|
|
503
551
|
const softDelete = resolveSoftDelete(softDeleteOption);
|
|
504
552
|
const m = middleware;
|
|
@@ -596,7 +644,7 @@ function createCrudRouter(config) {
|
|
|
596
644
|
});
|
|
597
645
|
return doGet();
|
|
598
646
|
}),
|
|
599
|
-
create: resolved.procedureFactory("create").input(
|
|
647
|
+
create: resolved.procedureFactory("create").input(schema).mutation(async ({ ctx, input }) => {
|
|
600
648
|
await withGuard(ctx, "create");
|
|
601
649
|
const doCreate = async (inputData) => {
|
|
602
650
|
const injectData = resolved.getInject(ctx, "create");
|
|
@@ -703,7 +751,7 @@ function createCrudRouter(config) {
|
|
|
703
751
|
});
|
|
704
752
|
return doUpdateMany(input.data);
|
|
705
753
|
}),
|
|
706
|
-
upsert: resolved.procedureFactory("upsert").input(
|
|
754
|
+
upsert: resolved.procedureFactory("upsert").input(schema).mutation(async ({ ctx, input }) => {
|
|
707
755
|
await withGuard(ctx, "upsert");
|
|
708
756
|
const doUpsert = async (inputData) => {
|
|
709
757
|
const inputId = inputData[idField];
|
package/dist/index.d.cts
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { InferInsertModel, InferSelectModel, SQL } from "drizzle-orm";
|
|
3
|
+
import { PgTable } from "drizzle-orm/pg-core";
|
|
1
4
|
import * as _trpc_server2 from "@trpc/server";
|
|
2
5
|
import { PostgresJsDatabase } from "drizzle-orm/postgres-js";
|
|
3
|
-
import { SQL } from "drizzle-orm";
|
|
4
|
-
import { PgTable } from "drizzle-orm/pg-core";
|
|
5
|
-
import { z } from "zod";
|
|
6
6
|
|
|
7
7
|
//#region src/trpc.d.ts
|
|
8
8
|
/**
|
|
@@ -23,6 +23,11 @@ declare const publicProcedure: _trpc_server2.TRPCProcedureBuilder<Context, objec
|
|
|
23
23
|
//#region src/types/config.d.ts
|
|
24
24
|
type CrudOperation = "list" | "get" | "create" | "update" | "delete" | "deleteMany" | "updateMany" | "upsert";
|
|
25
25
|
type WriteOperation = "create" | "update";
|
|
26
|
+
/**
|
|
27
|
+
* 表字段键类型提取
|
|
28
|
+
* 用于 omitFields 的强类型支持
|
|
29
|
+
*/
|
|
30
|
+
type TableColumnKeys<T extends PgTable> = keyof T["_"]["columns"];
|
|
26
31
|
type AnyProcedure = any;
|
|
27
32
|
interface SoftDeleteConfig {
|
|
28
33
|
/** 软删除标记列名 */
|
|
@@ -270,13 +275,37 @@ type ProcedureConfig = AnyProcedure | ProcedureMap | ProcedureFactory;
|
|
|
270
275
|
* authorize: (ctx, resource) => resource.ownerId === ctx.user.id,
|
|
271
276
|
* })
|
|
272
277
|
*/
|
|
273
|
-
interface CrudRouterConfig<TContext = unknown, TSelect = unknown, TInsert = unknown, TUpdate = unknown> {
|
|
278
|
+
interface CrudRouterConfig<TTable extends PgTable = PgTable, TContext = unknown, TSelect = unknown, TInsert = unknown, TUpdate = unknown> {
|
|
274
279
|
/** Drizzle 表定义 */
|
|
275
|
-
table:
|
|
276
|
-
/**
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
+
table: TTable;
|
|
281
|
+
/**
|
|
282
|
+
* 主 Schema(用于 create/upsert 输入验证)
|
|
283
|
+
* - 如果不传,自动从 table 派生并排除 omitFields
|
|
284
|
+
* - updateSchema 默认为 schema.partial().refine(nonEmpty)
|
|
285
|
+
*
|
|
286
|
+
* @example
|
|
287
|
+
* // 自动派生
|
|
288
|
+
* createCrudRouter({ table: tasks })
|
|
289
|
+
*
|
|
290
|
+
* // 显式传入
|
|
291
|
+
* createCrudRouter({ table: tasks, schema: taskSchema })
|
|
292
|
+
*/
|
|
293
|
+
schema?: z.ZodType<TInsert>;
|
|
294
|
+
/**
|
|
295
|
+
* 更新输入 Schema(可选)
|
|
296
|
+
* - 如果不传,自动从 schema.partial() 派生
|
|
297
|
+
* - 传入时覆盖自动派生
|
|
298
|
+
*
|
|
299
|
+
* @example
|
|
300
|
+
* createCrudRouter({
|
|
301
|
+
* table: tasks,
|
|
302
|
+
* schema: taskSchema,
|
|
303
|
+
* updateSchema: customUpdateSchema, // 覆盖自动派生
|
|
304
|
+
* })
|
|
305
|
+
*/
|
|
306
|
+
updateSchema?: z.ZodType<TUpdate>;
|
|
307
|
+
/** 查询返回 Schema */
|
|
308
|
+
selectSchema?: z.ZodType<TSelect>;
|
|
280
309
|
/**
|
|
281
310
|
* Procedure 配置(统一字段)
|
|
282
311
|
*
|
|
@@ -320,8 +349,11 @@ interface CrudRouterConfig<TContext = unknown, TSelect = unknown, TInsert = unkn
|
|
|
320
349
|
* authorize: (ctx, resource, op) => resource.ownerId === ctx.user.id
|
|
321
350
|
*/
|
|
322
351
|
authorize?: (ctx: TContext, resource: TSelect, operation: CrudOperation) => boolean | Promise<boolean>;
|
|
323
|
-
/**
|
|
324
|
-
|
|
352
|
+
/**
|
|
353
|
+
* 要从 schema 排除的字段(自动派生时使用)
|
|
354
|
+
* @default ["id", "createdAt", "updatedAt"]
|
|
355
|
+
*/
|
|
356
|
+
omitFields?: TableColumnKeys<TTable>[];
|
|
325
357
|
/** ID 字段名,默认 "id" */
|
|
326
358
|
idField?: string;
|
|
327
359
|
/** 可过滤的列白名单 */
|
|
@@ -390,17 +422,33 @@ interface CrudProcedures<TSelect, TInsert, TUpdate> {
|
|
|
390
422
|
}
|
|
391
423
|
//#endregion
|
|
392
424
|
//#region src/routers/_factory.d.ts
|
|
425
|
+
/**
|
|
426
|
+
* createCrudRouter 返回类型
|
|
427
|
+
*/
|
|
428
|
+
type CrudRouterReturn<TSelect, TInsert, TUpdate> = ReturnType<typeof router> & {
|
|
429
|
+
procedures: CrudProcedures<TSelect, TInsert, TUpdate>;
|
|
430
|
+
};
|
|
393
431
|
/**
|
|
394
432
|
* 创建 CRUD Router
|
|
395
433
|
*
|
|
396
434
|
* @typeParam TContext - 上下文类型(包含 db)
|
|
397
|
-
* @typeParam TSelect -
|
|
398
|
-
* @typeParam TInsert - 创建输入类型(从
|
|
399
|
-
* @typeParam TUpdate - 更新输入类型(从 updateSchema 推断)
|
|
435
|
+
* @typeParam TSelect - 查询返回类型
|
|
436
|
+
* @typeParam TInsert - 创建输入类型(从 schema 推断)
|
|
437
|
+
* @typeParam TUpdate - 更新输入类型(从 updateSchema 或 schema.partial() 推断)
|
|
400
438
|
*/
|
|
401
|
-
declare function createCrudRouter<
|
|
402
|
-
|
|
403
|
-
|
|
439
|
+
declare function createCrudRouter<TTable extends PgTable, TContext, TSelect, TInsert, TUpdate>(config: CrudRouterConfig<TTable, TContext, TSelect, TInsert, TUpdate> & {
|
|
440
|
+
schema: z.ZodType<TInsert>;
|
|
441
|
+
updateSchema: z.ZodType<TUpdate>;
|
|
442
|
+
}): CrudRouterReturn<TSelect, TInsert, TUpdate>;
|
|
443
|
+
declare function createCrudRouter<TTable extends PgTable, TContext = unknown>(config: Omit<CrudRouterConfig<TTable, TContext>, "schema" | "updateSchema"> & {
|
|
444
|
+
schema?: undefined;
|
|
445
|
+
updateSchema?: undefined;
|
|
446
|
+
}): CrudRouterReturn<InferSelectModel<TTable>, InferInsertModel<TTable>, Partial<InferInsertModel<TTable>>>;
|
|
447
|
+
declare function createCrudRouter<TTable extends PgTable, TContext, TInsert>(config: Omit<CrudRouterConfig<TTable, TContext, unknown, TInsert, unknown>, "updateSchema"> & {
|
|
448
|
+
schema: z.ZodType<TInsert>;
|
|
449
|
+
updateSchema?: undefined;
|
|
450
|
+
}): CrudRouterReturn<unknown, TInsert, Partial<TInsert>>;
|
|
451
|
+
declare function createCrudRouter<TTable extends PgTable, TContext = unknown, TSelect = unknown, TInsert = unknown, TUpdate = unknown>(config: CrudRouterConfig<TTable, TContext, TSelect, TInsert, TUpdate>): CrudRouterReturn<TSelect, TInsert, TUpdate>;
|
|
404
452
|
//#endregion
|
|
405
453
|
//#region src/lib/middleware-helpers.d.ts
|
|
406
454
|
/**
|
package/dist/index.d.ts
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
|
-
import { SQL } from "drizzle-orm";
|
|
2
|
+
import { InferInsertModel, InferSelectModel, SQL } from "drizzle-orm";
|
|
3
3
|
import * as _trpc_server2 from "@trpc/server";
|
|
4
4
|
import superjson from "superjson";
|
|
5
|
-
import { PostgresJsDatabase } from "drizzle-orm/postgres-js";
|
|
6
5
|
import { PgTable } from "drizzle-orm/pg-core";
|
|
6
|
+
import { PostgresJsDatabase } from "drizzle-orm/postgres-js";
|
|
7
7
|
|
|
8
8
|
//#region src/trpc.d.ts
|
|
9
9
|
/**
|
|
@@ -24,6 +24,11 @@ declare const publicProcedure: _trpc_server2.TRPCProcedureBuilder<Context, objec
|
|
|
24
24
|
//#region src/types/config.d.ts
|
|
25
25
|
type CrudOperation = "list" | "get" | "create" | "update" | "delete" | "deleteMany" | "updateMany" | "upsert";
|
|
26
26
|
type WriteOperation = "create" | "update";
|
|
27
|
+
/**
|
|
28
|
+
* 表字段键类型提取
|
|
29
|
+
* 用于 omitFields 的强类型支持
|
|
30
|
+
*/
|
|
31
|
+
type TableColumnKeys<T extends PgTable> = keyof T["_"]["columns"];
|
|
27
32
|
type AnyProcedure = any;
|
|
28
33
|
interface SoftDeleteConfig {
|
|
29
34
|
/** 软删除标记列名 */
|
|
@@ -271,13 +276,37 @@ type ProcedureConfig = AnyProcedure | ProcedureMap | ProcedureFactory;
|
|
|
271
276
|
* authorize: (ctx, resource) => resource.ownerId === ctx.user.id,
|
|
272
277
|
* })
|
|
273
278
|
*/
|
|
274
|
-
interface CrudRouterConfig<TContext = unknown, TSelect = unknown, TInsert = unknown, TUpdate = unknown> {
|
|
279
|
+
interface CrudRouterConfig<TTable extends PgTable = PgTable, TContext = unknown, TSelect = unknown, TInsert = unknown, TUpdate = unknown> {
|
|
275
280
|
/** Drizzle 表定义 */
|
|
276
|
-
table:
|
|
277
|
-
/**
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
+
table: TTable;
|
|
282
|
+
/**
|
|
283
|
+
* 主 Schema(用于 create/upsert 输入验证)
|
|
284
|
+
* - 如果不传,自动从 table 派生并排除 omitFields
|
|
285
|
+
* - updateSchema 默认为 schema.partial().refine(nonEmpty)
|
|
286
|
+
*
|
|
287
|
+
* @example
|
|
288
|
+
* // 自动派生
|
|
289
|
+
* createCrudRouter({ table: tasks })
|
|
290
|
+
*
|
|
291
|
+
* // 显式传入
|
|
292
|
+
* createCrudRouter({ table: tasks, schema: taskSchema })
|
|
293
|
+
*/
|
|
294
|
+
schema?: z.ZodType<TInsert>;
|
|
295
|
+
/**
|
|
296
|
+
* 更新输入 Schema(可选)
|
|
297
|
+
* - 如果不传,自动从 schema.partial() 派生
|
|
298
|
+
* - 传入时覆盖自动派生
|
|
299
|
+
*
|
|
300
|
+
* @example
|
|
301
|
+
* createCrudRouter({
|
|
302
|
+
* table: tasks,
|
|
303
|
+
* schema: taskSchema,
|
|
304
|
+
* updateSchema: customUpdateSchema, // 覆盖自动派生
|
|
305
|
+
* })
|
|
306
|
+
*/
|
|
307
|
+
updateSchema?: z.ZodType<TUpdate>;
|
|
308
|
+
/** 查询返回 Schema */
|
|
309
|
+
selectSchema?: z.ZodType<TSelect>;
|
|
281
310
|
/**
|
|
282
311
|
* Procedure 配置(统一字段)
|
|
283
312
|
*
|
|
@@ -321,8 +350,11 @@ interface CrudRouterConfig<TContext = unknown, TSelect = unknown, TInsert = unkn
|
|
|
321
350
|
* authorize: (ctx, resource, op) => resource.ownerId === ctx.user.id
|
|
322
351
|
*/
|
|
323
352
|
authorize?: (ctx: TContext, resource: TSelect, operation: CrudOperation) => boolean | Promise<boolean>;
|
|
324
|
-
/**
|
|
325
|
-
|
|
353
|
+
/**
|
|
354
|
+
* 要从 schema 排除的字段(自动派生时使用)
|
|
355
|
+
* @default ["id", "createdAt", "updatedAt"]
|
|
356
|
+
*/
|
|
357
|
+
omitFields?: TableColumnKeys<TTable>[];
|
|
326
358
|
/** ID 字段名,默认 "id" */
|
|
327
359
|
idField?: string;
|
|
328
360
|
/** 可过滤的列白名单 */
|
|
@@ -391,17 +423,33 @@ interface CrudProcedures<TSelect, TInsert, TUpdate> {
|
|
|
391
423
|
}
|
|
392
424
|
//#endregion
|
|
393
425
|
//#region src/routers/_factory.d.ts
|
|
426
|
+
/**
|
|
427
|
+
* createCrudRouter 返回类型
|
|
428
|
+
*/
|
|
429
|
+
type CrudRouterReturn<TSelect, TInsert, TUpdate> = ReturnType<typeof router> & {
|
|
430
|
+
procedures: CrudProcedures<TSelect, TInsert, TUpdate>;
|
|
431
|
+
};
|
|
394
432
|
/**
|
|
395
433
|
* 创建 CRUD Router
|
|
396
434
|
*
|
|
397
435
|
* @typeParam TContext - 上下文类型(包含 db)
|
|
398
|
-
* @typeParam TSelect -
|
|
399
|
-
* @typeParam TInsert - 创建输入类型(从
|
|
400
|
-
* @typeParam TUpdate - 更新输入类型(从 updateSchema 推断)
|
|
436
|
+
* @typeParam TSelect - 查询返回类型
|
|
437
|
+
* @typeParam TInsert - 创建输入类型(从 schema 推断)
|
|
438
|
+
* @typeParam TUpdate - 更新输入类型(从 updateSchema 或 schema.partial() 推断)
|
|
401
439
|
*/
|
|
402
|
-
declare function createCrudRouter<
|
|
403
|
-
|
|
404
|
-
|
|
440
|
+
declare function createCrudRouter<TTable extends PgTable, TContext, TSelect, TInsert, TUpdate>(config: CrudRouterConfig<TTable, TContext, TSelect, TInsert, TUpdate> & {
|
|
441
|
+
schema: z.ZodType<TInsert>;
|
|
442
|
+
updateSchema: z.ZodType<TUpdate>;
|
|
443
|
+
}): CrudRouterReturn<TSelect, TInsert, TUpdate>;
|
|
444
|
+
declare function createCrudRouter<TTable extends PgTable, TContext = unknown>(config: Omit<CrudRouterConfig<TTable, TContext>, "schema" | "updateSchema"> & {
|
|
445
|
+
schema?: undefined;
|
|
446
|
+
updateSchema?: undefined;
|
|
447
|
+
}): CrudRouterReturn<InferSelectModel<TTable>, InferInsertModel<TTable>, Partial<InferInsertModel<TTable>>>;
|
|
448
|
+
declare function createCrudRouter<TTable extends PgTable, TContext, TInsert>(config: Omit<CrudRouterConfig<TTable, TContext, unknown, TInsert, unknown>, "updateSchema"> & {
|
|
449
|
+
schema: z.ZodType<TInsert>;
|
|
450
|
+
updateSchema?: undefined;
|
|
451
|
+
}): CrudRouterReturn<unknown, TInsert, Partial<TInsert>>;
|
|
452
|
+
declare function createCrudRouter<TTable extends PgTable, TContext = unknown, TSelect = unknown, TInsert = unknown, TUpdate = unknown>(config: CrudRouterConfig<TTable, TContext, TSelect, TInsert, TUpdate>): CrudRouterReturn<TSelect, TInsert, TUpdate>;
|
|
405
453
|
//#endregion
|
|
406
454
|
//#region src/lib/middleware-helpers.d.ts
|
|
407
455
|
/**
|
package/dist/index.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
2
|
import { and, asc, desc, eq, gt, gte, ilike, inArray, isNull, lt, lte, ne, not, notIlike, notInArray, or, sql } from "drizzle-orm";
|
|
3
|
+
import { createInsertSchema, createSelectSchema } from "drizzle-zod";
|
|
3
4
|
import { TRPCError, initTRPC } from "@trpc/server";
|
|
4
5
|
import superjson from "superjson";
|
|
5
6
|
import { addDays, endOfDay, startOfDay } from "date-fns";
|
|
@@ -371,6 +372,26 @@ const dataTableConfig = {
|
|
|
371
372
|
joinOperators: ["and", "or"]
|
|
372
373
|
};
|
|
373
374
|
|
|
375
|
+
//#endregion
|
|
376
|
+
//#region src/lib/schema-utils.ts
|
|
377
|
+
/**
|
|
378
|
+
* 检查是否为 ZodObject 类型
|
|
379
|
+
* 兼容 Zod v3 和 v4
|
|
380
|
+
*
|
|
381
|
+
* 实现策略:检查 schema 是否具有 ZodObject 特有的 .shape 属性
|
|
382
|
+
* 这种方式比检查内部 _def 属性更稳定,因为不依赖 Zod 的内部实现细节
|
|
383
|
+
*/
|
|
384
|
+
function isZodObject(schema) {
|
|
385
|
+
return schema !== null && typeof schema === "object" && "shape" in schema && typeof schema.shape === "object" && schema.shape !== null;
|
|
386
|
+
}
|
|
387
|
+
/**
|
|
388
|
+
* 非空对象验证器
|
|
389
|
+
* 用于 updateSchema 的 refine,防止空更新
|
|
390
|
+
*/
|
|
391
|
+
function nonEmpty(value) {
|
|
392
|
+
return Object.keys(value).length > 0;
|
|
393
|
+
}
|
|
394
|
+
|
|
374
395
|
//#endregion
|
|
375
396
|
//#region src/routers/_factory.ts
|
|
376
397
|
function resolveSoftDelete(option) {
|
|
@@ -457,20 +478,46 @@ const listInputSchema = z.object({
|
|
|
457
478
|
filters: z.array(filterItemSchema).optional(),
|
|
458
479
|
joinOperator: z.enum(["and", "or"]).default("and")
|
|
459
480
|
});
|
|
481
|
+
const DEFAULT_OMIT_FIELDS = [
|
|
482
|
+
"id",
|
|
483
|
+
"createdAt",
|
|
484
|
+
"updatedAt"
|
|
485
|
+
];
|
|
486
|
+
/**
|
|
487
|
+
* 解析并派生 Schema
|
|
488
|
+
* - 优先使用显式传入的 Schema
|
|
489
|
+
* - 否则从 Drizzle table 自动派生
|
|
490
|
+
*/
|
|
491
|
+
function resolveSchemas(config) {
|
|
492
|
+
const { table, omitFields = DEFAULT_OMIT_FIELDS } = config;
|
|
493
|
+
let schema;
|
|
494
|
+
if (config.schema) {
|
|
495
|
+
if (!isZodObject(config.schema)) throw new Error("[createCrudRouter] schema must be a ZodObject (not ZodEffects or other types). If you're using .refine() or .transform(), please remove them from schema.");
|
|
496
|
+
schema = config.schema;
|
|
497
|
+
} else {
|
|
498
|
+
const rawSchema = createInsertSchema(table);
|
|
499
|
+
if (!isZodObject(rawSchema)) throw new Error("[createCrudRouter] drizzle-zod returned non-ZodObject schema. This may be due to table refinements. Please provide schema explicitly.");
|
|
500
|
+
const omitConfig = Object.fromEntries(omitFields.map((f) => [f, true]));
|
|
501
|
+
schema = rawSchema.omit(omitConfig);
|
|
502
|
+
}
|
|
503
|
+
let selectSchema;
|
|
504
|
+
if (config.selectSchema) selectSchema = config.selectSchema;
|
|
505
|
+
else if (config.schema) selectSchema = schema;
|
|
506
|
+
else selectSchema = createSelectSchema(table);
|
|
507
|
+
const updateSchema = config.updateSchema ?? schema.partial().refine(nonEmpty, { message: "Update payload cannot be empty. At least one field is required." });
|
|
508
|
+
return {
|
|
509
|
+
selectSchema,
|
|
510
|
+
schema,
|
|
511
|
+
updateSchema
|
|
512
|
+
};
|
|
513
|
+
}
|
|
460
514
|
function validateColumn(columnId, allowedColumns) {
|
|
461
515
|
if (!allowedColumns || allowedColumns.length === 0) return true;
|
|
462
516
|
return allowedColumns.includes(columnId);
|
|
463
517
|
}
|
|
464
|
-
/**
|
|
465
|
-
* 创建 CRUD Router
|
|
466
|
-
*
|
|
467
|
-
* @typeParam TContext - 上下文类型(包含 db)
|
|
468
|
-
* @typeParam TSelect - 查询返回类型(从 selectSchema 或 insertSchema 推断)
|
|
469
|
-
* @typeParam TInsert - 创建输入类型(从 insertSchema 推断)
|
|
470
|
-
* @typeParam TUpdate - 更新输入类型(从 updateSchema 推断)
|
|
471
|
-
*/
|
|
472
518
|
function createCrudRouter(config) {
|
|
473
|
-
const { table,
|
|
519
|
+
const { table, idField = "id", filterableColumns, sortableColumns, maxBatchSize = 100, softDelete: softDeleteOption, middleware = {} } = config;
|
|
520
|
+
const { schema, updateSchema } = resolveSchemas(config);
|
|
474
521
|
const resolved = resolveConfig(config);
|
|
475
522
|
const softDelete = resolveSoftDelete(softDeleteOption);
|
|
476
523
|
const m = middleware;
|
|
@@ -568,7 +615,7 @@ function createCrudRouter(config) {
|
|
|
568
615
|
});
|
|
569
616
|
return doGet();
|
|
570
617
|
}),
|
|
571
|
-
create: resolved.procedureFactory("create").input(
|
|
618
|
+
create: resolved.procedureFactory("create").input(schema).mutation(async ({ ctx, input }) => {
|
|
572
619
|
await withGuard(ctx, "create");
|
|
573
620
|
const doCreate = async (inputData) => {
|
|
574
621
|
const injectData = resolved.getInject(ctx, "create");
|
|
@@ -675,7 +722,7 @@ function createCrudRouter(config) {
|
|
|
675
722
|
});
|
|
676
723
|
return doUpdateMany(input.data);
|
|
677
724
|
}),
|
|
678
|
-
upsert: resolved.procedureFactory("upsert").input(
|
|
725
|
+
upsert: resolved.procedureFactory("upsert").input(schema).mutation(async ({ ctx, input }) => {
|
|
679
726
|
await withGuard(ctx, "upsert");
|
|
680
727
|
const doUpsert = async (inputData) => {
|
|
681
728
|
const inputId = inputData[idField];
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@wordrhyme/auto-crud-server",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "0.
|
|
4
|
+
"version": "0.9.0",
|
|
5
5
|
"description": "tRPC server utilities for auto-crud - automatic CRUD routers for Drizzle ORM",
|
|
6
6
|
"author": "wordrhyme",
|
|
7
7
|
"license": "MIT",
|
|
@@ -50,8 +50,8 @@
|
|
|
50
50
|
"@internal/eslint-config": "0.3.0",
|
|
51
51
|
"@internal/tsconfig": "0.1.0",
|
|
52
52
|
"@internal/prettier-config": "0.0.1",
|
|
53
|
-
"@internal/
|
|
54
|
-
"@internal/
|
|
53
|
+
"@internal/tsdown-config": "0.1.0",
|
|
54
|
+
"@internal/vitest-config": "0.1.0"
|
|
55
55
|
},
|
|
56
56
|
"prettier": "@internal/prettier-config",
|
|
57
57
|
"scripts": {
|