@wordrhyme/auto-crud-server 0.6.0 → 0.6.1

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
@@ -136,7 +136,7 @@ export { handler as GET, handler as POST };
136
136
 
137
137
  ## 📖 自动生成的路由
138
138
 
139
- `createCrudRouter` 会自动生成以下 7 个路由:
139
+ `createCrudRouter` 会自动生成以下 8 个路由:
140
140
 
141
141
  ### 1. `list` - 列表查询
142
142
 
@@ -299,6 +299,42 @@ await trpc.tasks.updateMany({
299
299
  });
300
300
  ```
301
301
 
302
+ ### 8. `upsert` - 存在则更新,不存在则创建
303
+
304
+ **输入**:
305
+ ```typescript
306
+ Omit<Task, "createdAt" | "updatedAt"> // 需要包含 id
307
+ ```
308
+
309
+ **输出**:
310
+ ```typescript
311
+ {
312
+ data: Task; // 创建或更新后的记录
313
+ isNew: boolean; // true = 新建, false = 更新
314
+ }
315
+ ```
316
+
317
+ **示例**:
318
+ ```typescript
319
+ // 如果 id="123" 存在则更新,不存在则创建
320
+ const { data, isNew } = await trpc.tasks.upsert({
321
+ id: "123",
322
+ title: "My Task",
323
+ status: "todo",
324
+ });
325
+
326
+ if (isNew) {
327
+ console.log("Created new task");
328
+ } else {
329
+ console.log("Updated existing task");
330
+ }
331
+ ```
332
+
333
+ **适用场景**:
334
+ - 同步外部数据
335
+ - 幂等导入
336
+ - 配置项更新
337
+
302
338
  ---
303
339
 
304
340
  ## 🎯 高级过滤
package/dist/index.cjs CHANGED
@@ -474,6 +474,14 @@ function validateColumn(columnId, allowedColumns) {
474
474
  if (!allowedColumns || allowedColumns.length === 0) return true;
475
475
  return allowedColumns.includes(columnId);
476
476
  }
477
+ /**
478
+ * 创建 CRUD Router
479
+ *
480
+ * @typeParam TContext - 上下文类型(包含 db)
481
+ * @typeParam TSelect - 查询返回类型(从 selectSchema 或 insertSchema 推断)
482
+ * @typeParam TInsert - 创建输入类型(从 insertSchema 推断)
483
+ * @typeParam TUpdate - 更新输入类型(从 updateSchema 推断)
484
+ */
477
485
  function createCrudRouter(config) {
478
486
  const { table, insertSchema, updateSchema, idField = "id", filterableColumns, sortableColumns, maxBatchSize = 100, softDelete: softDeleteOption, middleware = {} } = config;
479
487
  const resolved = resolveConfig(config);
@@ -496,6 +504,12 @@ function createCrudRouter(config) {
496
504
  };
497
505
  const withGuard = async (ctx, operation) => {
498
506
  if (resolved.guard) {
507
+ if (operation === "upsert") {
508
+ const canCreate = await resolved.guard(ctx, "create");
509
+ const canUpdate = await resolved.guard(ctx, "update");
510
+ if (!canCreate || !canUpdate) throw new Error("Forbidden: upsert requires both create and update permissions");
511
+ return;
512
+ }
499
513
  if (!await resolved.guard(ctx, operation)) throw new Error(`Forbidden: ${operation} not allowed`);
500
514
  }
501
515
  };
@@ -543,7 +557,7 @@ function createCrudRouter(config) {
543
557
  });
544
558
  return doList(listInput);
545
559
  }),
546
- getById: resolved.procedureFactory("get").input(zod.z.string()).query(async ({ ctx, input }) => {
560
+ get: resolved.procedureFactory("get").input(zod.z.string()).query(async ({ ctx, input }) => {
547
561
  await withGuard(ctx, "get");
548
562
  const doGet = async () => {
549
563
  const where = buildWhere(ctx, "get", (0, drizzle_orm.eq)(getIdColumn(), input));
@@ -658,6 +672,37 @@ function createCrudRouter(config) {
658
672
  next: async (modifiedData) => doUpdateMany(modifiedData ?? input.data)
659
673
  });
660
674
  return doUpdateMany(input.data);
675
+ }),
676
+ upsert: resolved.procedureFactory("upsert").input(insertSchema).mutation(async ({ ctx, input }) => {
677
+ await withGuard(ctx, "upsert");
678
+ const doUpsert = async (inputData) => {
679
+ const injectData = resolved.getInject(ctx, "create");
680
+ const data = {
681
+ ...inputData,
682
+ ...injectData
683
+ };
684
+ const inputId = inputData[idField];
685
+ let isNew = true;
686
+ if (inputId) {
687
+ const where = buildWhere(ctx, "get", (0, drizzle_orm.eq)(getIdColumn(), inputId));
688
+ const [existing] = await ctx.db.select().from(table).where(where);
689
+ isNew = !existing;
690
+ }
691
+ const [result] = await ctx.db.insert(table).values(data).onConflictDoUpdate({
692
+ target: getIdColumn(),
693
+ set: data
694
+ }).returning();
695
+ return {
696
+ data: result,
697
+ isNew
698
+ };
699
+ };
700
+ if (m.upsert) return m.upsert({
701
+ ctx,
702
+ input,
703
+ next: async (modifiedInput) => doUpsert(modifiedInput ?? input)
704
+ });
705
+ return doUpsert(input);
661
706
  })
662
707
  };
663
708
  const crudRouter = router(procedures);
@@ -667,6 +712,10 @@ function createCrudRouter(config) {
667
712
  //#endregion
668
713
  //#region src/lib/middleware-helpers.ts
669
714
  /**
715
+ * Middleware 便捷工具函数
716
+ * 简化常见场景的 middleware 创建
717
+ */
718
+ /**
670
719
  * 创建 after-only 的 middleware(最常见场景)
671
720
  * 在操作完成后执行副作用,不修改返回值
672
721
  *
@@ -679,28 +728,13 @@ function createCrudRouter(config) {
679
728
  * }
680
729
  */
681
730
  function afterMiddleware(fn) {
682
- return async ({ ctx, next }) => {
683
- const result = await next();
684
- await fn(ctx, result);
731
+ return async (params) => {
732
+ const result = await params.next();
733
+ await fn(params.ctx, result);
685
734
  return result;
686
735
  };
687
736
  }
688
737
  /**
689
- * 创建 after-only 的 middleware,可修改返回值
690
- *
691
- * @example
692
- * middleware: {
693
- * get: afterMiddlewareTransform(async (ctx, result) => {
694
- * return { ...result, computed: calculateSomething(result) };
695
- * }),
696
- * }
697
- */
698
- function afterMiddlewareTransform(fn) {
699
- return async ({ ctx, next }) => {
700
- return fn(ctx, await next());
701
- };
702
- }
703
- /**
704
738
  * 创建 before-only 的 middleware
705
739
  * 在操作执行前修改输入数据
706
740
  *
@@ -712,8 +746,9 @@ function afterMiddlewareTransform(fn) {
712
746
  * }
713
747
  */
714
748
  function beforeMiddleware(fn) {
715
- return async ({ ctx, input, next }) => {
716
- return next(await fn(ctx, input));
749
+ return async (params) => {
750
+ const modifiedInput = await fn(params.ctx, params.input);
751
+ return params.next(modifiedInput);
717
752
  };
718
753
  }
719
754
  /**
@@ -730,69 +765,25 @@ function beforeMiddleware(fn) {
730
765
  function composeMiddleware(...middlewares) {
731
766
  return async (params) => {
732
767
  let index = 0;
733
- const executeNext = async (input) => {
734
- if (index >= middlewares.length) return params.next(input);
768
+ const executeNext = async (...args) => {
769
+ if (index >= middlewares.length) return params.next(...args);
735
770
  const currentMiddleware = middlewares[index++];
736
771
  return currentMiddleware({
737
- ctx: params.ctx,
738
- input,
772
+ ...params,
739
773
  next: executeNext
740
774
  });
741
775
  };
742
- return executeNext(params.input);
776
+ return executeNext();
743
777
  };
744
778
  }
745
- /**
746
- * 创建 list 操作的 after middleware
747
- */
748
- function afterList(fn) {
749
- return afterMiddleware(fn);
750
- }
751
- /**
752
- * 创建 list 操作的 before middleware
753
- */
754
- function beforeList(fn) {
755
- return beforeMiddleware(fn);
756
- }
757
- /**
758
- * 创建 create 操作的 after middleware
759
- */
760
- function afterCreate(fn) {
761
- return afterMiddleware(fn);
762
- }
763
- /**
764
- * 创建 create 操作的 before middleware
765
- */
766
- function beforeCreate(fn) {
767
- return beforeMiddleware(fn);
768
- }
769
- /**
770
- * 创建 update 操作的 after middleware
771
- */
772
- function afterUpdate(fn) {
773
- return afterMiddleware(fn);
774
- }
775
- /**
776
- * 创建 delete 操作的 after middleware
777
- */
778
- function afterDelete(fn) {
779
- return afterMiddleware(fn);
780
- }
781
779
 
782
780
  //#endregion
783
781
  //#region src/routers/index.ts
784
782
  const appRouter = router({});
785
783
 
786
784
  //#endregion
787
- exports.afterCreate = afterCreate;
788
- exports.afterDelete = afterDelete;
789
- exports.afterList = afterList;
790
785
  exports.afterMiddleware = afterMiddleware;
791
- exports.afterMiddlewareTransform = afterMiddlewareTransform;
792
- exports.afterUpdate = afterUpdate;
793
786
  exports.appRouter = appRouter;
794
- exports.beforeCreate = beforeCreate;
795
- exports.beforeList = beforeList;
796
787
  exports.beforeMiddleware = beforeMiddleware;
797
788
  exports.composeMiddleware = composeMiddleware;
798
789
  exports.createCrudRouter = createCrudRouter;
package/dist/index.d.cts CHANGED
@@ -1,6 +1,5 @@
1
- import * as _trpc_server5 from "@trpc/server";
1
+ import * as _trpc_server2 from "@trpc/server";
2
2
  import { PostgresJsDatabase } from "drizzle-orm/postgres-js";
3
- import * as node_modules__trpc_server_dist_unstable_core_do_not_import_d_CjQPvBRI_mjs0 from "node_modules/@trpc/server/dist/unstable-core-do-not-import.d-CjQPvBRI.mjs";
4
3
  import { SQL } from "drizzle-orm";
5
4
  import { PgTable } from "drizzle-orm/pg-core";
6
5
  import { z } from "zod";
@@ -13,16 +12,16 @@ import { z } from "zod";
13
12
  interface Context {
14
13
  db: PostgresJsDatabase<Record<string, never>>;
15
14
  }
16
- declare const router: _trpc_server5.TRPCRouterBuilder<{
15
+ declare const router: _trpc_server2.TRPCRouterBuilder<{
17
16
  ctx: Context;
18
17
  meta: object;
19
- errorShape: _trpc_server5.TRPCDefaultErrorShape;
18
+ errorShape: _trpc_server2.TRPCDefaultErrorShape;
20
19
  transformer: true;
21
20
  }>;
22
- declare const publicProcedure: _trpc_server5.TRPCProcedureBuilder<Context, object, object, _trpc_server5.TRPCUnsetMarker, _trpc_server5.TRPCUnsetMarker, _trpc_server5.TRPCUnsetMarker, _trpc_server5.TRPCUnsetMarker, false>;
21
+ declare const publicProcedure: _trpc_server2.TRPCProcedureBuilder<Context, object, object, _trpc_server2.TRPCUnsetMarker, _trpc_server2.TRPCUnsetMarker, _trpc_server2.TRPCUnsetMarker, _trpc_server2.TRPCUnsetMarker, false>;
23
22
  //#endregion
24
23
  //#region src/types/config.d.ts
25
- type CrudOperation = "list" | "get" | "create" | "update" | "delete" | "deleteMany" | "updateMany";
24
+ type CrudOperation = "list" | "get" | "create" | "update" | "delete" | "deleteMany" | "updateMany" | "upsert";
26
25
  type WriteOperation = "create" | "update";
27
26
  type AnyProcedure = any;
28
27
  interface SoftDeleteConfig {
@@ -71,6 +70,57 @@ interface ListResult<TSelect> {
71
70
  perPage: number;
72
71
  pageCount: number;
73
72
  }
73
+ interface ListMiddlewareParams<TContext, TSelect> {
74
+ ctx: TContext;
75
+ input: ListInput;
76
+ next: (input?: ListInput) => Promise<ListResult<TSelect>>;
77
+ }
78
+ interface GetMiddlewareParams<TContext, TSelect> {
79
+ ctx: TContext;
80
+ id: string;
81
+ next: () => Promise<TSelect | null>;
82
+ }
83
+ interface CreateMiddlewareParams<TContext, TSelect, TInsert> {
84
+ ctx: TContext;
85
+ input: TInsert;
86
+ next: (input?: TInsert) => Promise<TSelect>;
87
+ }
88
+ interface UpdateMiddlewareParams<TContext, TSelect, TUpdate> {
89
+ ctx: TContext;
90
+ id: string;
91
+ data: TUpdate;
92
+ existing: TSelect;
93
+ next: (data?: TUpdate) => Promise<TSelect>;
94
+ }
95
+ interface DeleteMiddlewareParams<TContext, TSelect> {
96
+ ctx: TContext;
97
+ id: string;
98
+ existing: TSelect;
99
+ next: () => Promise<TSelect>;
100
+ }
101
+ interface DeleteManyMiddlewareParams<TContext> {
102
+ ctx: TContext;
103
+ ids: string[];
104
+ next: () => Promise<{
105
+ deleted: number;
106
+ }>;
107
+ }
108
+ interface UpdateManyMiddlewareParams<TContext, TUpdate> {
109
+ ctx: TContext;
110
+ ids: string[];
111
+ data: TUpdate;
112
+ next: (data?: TUpdate) => Promise<{
113
+ updated: number;
114
+ }>;
115
+ }
116
+ interface UpsertMiddlewareParams<TContext, TSelect, TInsert> {
117
+ ctx: TContext;
118
+ input: TInsert;
119
+ next: (input?: TInsert) => Promise<{
120
+ data: TSelect;
121
+ isNew: boolean;
122
+ }>;
123
+ }
74
124
  /**
75
125
  * 操作包装器 - 类似 tRPC middleware 的 next 模式
76
126
  *
@@ -81,6 +131,11 @@ interface ListResult<TSelect> {
81
131
  * - 条件拦截(不调用 next 直接返回/抛错)
82
132
  * - 包装事务(try/catch 包裹 next)
83
133
  *
134
+ * @typeParam TContext - 上下文类型
135
+ * @typeParam TSelect - 查询返回类型(从 selectSchema 推断)
136
+ * @typeParam TInsert - 创建输入类型(从 insertSchema 推断)
137
+ * @typeParam TUpdate - 更新输入类型(从 updateSchema 推断)
138
+ *
84
139
  * @example
85
140
  * // 修改输入 + 执行副作用
86
141
  * middleware: {
@@ -91,98 +146,60 @@ interface ListResult<TSelect> {
91
146
  * return result;
92
147
  * },
93
148
  * }
94
- *
95
- * @example
96
- * // 简单副作用 - 使用工具函数
97
- * import { afterMiddleware } from "@wordrhyme/auto-crud-server";
98
- *
99
- * middleware: {
100
- * create: afterMiddleware(async (ctx, result) => {
101
- * await sendEmail(result);
102
- * }),
103
- * }
104
149
  */
105
- interface CrudMiddleware<TContext = unknown> {
150
+ interface CrudMiddleware<TContext = unknown, TSelect = unknown, TInsert = unknown, TUpdate = unknown> {
106
151
  /**
107
152
  * 列表查询包装器
108
153
  */
109
- list?: (params: {
110
- ctx: TContext;
111
- input: ListInput;
112
- next: (input?: ListInput) => Promise<ListResult<unknown>>;
113
- }) => Promise<ListResult<unknown>>;
154
+ list?: (params: ListMiddlewareParams<TContext, TSelect>) => Promise<ListResult<TSelect>>;
114
155
  /**
115
156
  * 单条查询包装器
116
157
  */
117
- get?: (params: {
118
- ctx: TContext;
119
- id: string;
120
- next: () => Promise<unknown | null>;
121
- }) => Promise<unknown | null>;
158
+ get?: (params: GetMiddlewareParams<TContext, TSelect>) => Promise<TSelect | null>;
122
159
  /**
123
160
  * 创建包装器
124
161
  */
125
- create?: (params: {
126
- ctx: TContext;
127
- input: unknown;
128
- next: (input?: unknown) => Promise<unknown>;
129
- }) => Promise<unknown>;
162
+ create?: (params: CreateMiddlewareParams<TContext, TSelect, TInsert>) => Promise<TSelect>;
130
163
  /**
131
164
  * 更新包装器
132
165
  * existing: 更新前的记录
133
166
  */
134
- update?: (params: {
135
- ctx: TContext;
136
- id: string;
137
- data: unknown;
138
- existing: unknown;
139
- next: (data?: unknown) => Promise<unknown>;
140
- }) => Promise<unknown>;
167
+ update?: (params: UpdateMiddlewareParams<TContext, TSelect, TUpdate>) => Promise<TSelect>;
141
168
  /**
142
169
  * 删除包装器
143
170
  * existing: 删除前的记录
144
171
  */
145
- delete?: (params: {
146
- ctx: TContext;
147
- id: string;
148
- existing: unknown;
149
- next: () => Promise<unknown>;
150
- }) => Promise<unknown>;
172
+ delete?: (params: DeleteMiddlewareParams<TContext, TSelect>) => Promise<TSelect>;
151
173
  /**
152
174
  * 批量删除包装器
153
175
  */
154
- deleteMany?: (params: {
155
- ctx: TContext;
156
- ids: string[];
157
- next: () => Promise<{
158
- deleted: number;
159
- }>;
160
- }) => Promise<{
176
+ deleteMany?: (params: DeleteManyMiddlewareParams<TContext>) => Promise<{
161
177
  deleted: number;
162
178
  }>;
163
179
  /**
164
180
  * 批量更新包装器
165
181
  */
166
- updateMany?: (params: {
167
- ctx: TContext;
168
- ids: string[];
169
- data: unknown;
170
- next: (data?: unknown) => Promise<{
171
- updated: number;
172
- }>;
173
- }) => Promise<{
182
+ updateMany?: (params: UpdateManyMiddlewareParams<TContext, TUpdate>) => Promise<{
174
183
  updated: number;
175
184
  }>;
185
+ /**
186
+ * Upsert 包装器
187
+ * isNew: 是否为新建(冲突检测后)
188
+ */
189
+ upsert?: (params: UpsertMiddlewareParams<TContext, TSelect, TInsert>) => Promise<{
190
+ data: TSelect;
191
+ isNew: boolean;
192
+ }>;
176
193
  }
177
- interface CrudRouterConfigBase<TContext = unknown> {
194
+ interface CrudRouterConfigBase<TContext = unknown, TSelect = unknown, TInsert = unknown, TUpdate = unknown> {
178
195
  /** Drizzle 表定义 */
179
196
  table: PgTable;
180
197
  /** 查询返回 Schema */
181
- selectSchema?: z.ZodType;
198
+ selectSchema?: z.ZodType<TSelect>;
182
199
  /** 创建输入 Schema */
183
- insertSchema: z.ZodType;
200
+ insertSchema: z.ZodType<TInsert>;
184
201
  /** 更新输入 Schema */
185
- updateSchema: z.ZodType;
202
+ updateSchema: z.ZodType<TUpdate>;
186
203
  /** ID 字段名,默认 "id" */
187
204
  idField?: string;
188
205
  /** 可过滤的列白名单 */
@@ -237,9 +254,9 @@ interface CrudRouterConfigBase<TContext = unknown> {
237
254
  * }),
238
255
  * }
239
256
  */
240
- middleware?: CrudMiddleware<TContext>;
257
+ middleware?: CrudMiddleware<TContext, TSelect, TInsert, TUpdate>;
241
258
  }
242
- interface DeclarativeConfig<TContext = unknown> extends CrudRouterConfigBase<TContext> {
259
+ interface DeclarativeConfig<TContext = unknown, TSelect = unknown, TInsert = unknown, TUpdate = unknown> extends CrudRouterConfigBase<TContext, TSelect, TInsert, TUpdate> {
243
260
  mode?: "declarative";
244
261
  /**
245
262
  * 基础 procedure(认证)
@@ -277,9 +294,9 @@ interface DeclarativeConfig<TContext = unknown> extends CrudRouterConfigBase<TCo
277
294
  * @example
278
295
  * authorize: (ctx, resource, op) => resource.ownerId === ctx.user.id
279
296
  */
280
- authorize?: (ctx: TContext, resource: Record<string, unknown>, operation: CrudOperation) => boolean | Promise<boolean>;
297
+ authorize?: (ctx: TContext, resource: TSelect, operation: CrudOperation) => boolean | Promise<boolean>;
281
298
  }
282
- interface FactoryConfig<TContext = unknown> extends CrudRouterConfigBase<TContext> {
299
+ interface FactoryConfig<TContext = unknown, TSelect = unknown, TInsert = unknown, TUpdate = unknown> extends CrudRouterConfigBase<TContext, TSelect, TInsert, TUpdate> {
283
300
  mode: "factory";
284
301
  /**
285
302
  * Procedure 工厂函数
@@ -298,7 +315,7 @@ interface FactoryConfig<TContext = unknown> extends CrudRouterConfigBase<TContex
298
315
  */
299
316
  inject?: (ctx: TContext, operation: WriteOperation) => Record<string, unknown>;
300
317
  }
301
- interface ProceduresConfig<TContext = unknown> extends CrudRouterConfigBase<TContext> {
318
+ interface ProceduresConfig<TContext = unknown, TSelect = unknown, TInsert = unknown, TUpdate = unknown> extends CrudRouterConfigBase<TContext, TSelect, TInsert, TUpdate> {
302
319
  mode: "procedures";
303
320
  /**
304
321
  * 按操作指定不同的 procedure
@@ -322,49 +339,36 @@ interface ProceduresConfig<TContext = unknown> extends CrudRouterConfigBase<TCon
322
339
  */
323
340
  inject?: (ctx: TContext, operation: WriteOperation) => Record<string, unknown>;
324
341
  }
325
- type CrudRouterConfig<TContext = unknown> = DeclarativeConfig<TContext> | FactoryConfig<TContext> | ProceduresConfig<TContext>;
342
+ type CrudRouterConfig<TContext = unknown, TSelect = unknown, TInsert = unknown, TUpdate = unknown> = DeclarativeConfig<TContext, TSelect, TInsert, TUpdate> | FactoryConfig<TContext, TSelect, TInsert, TUpdate> | ProceduresConfig<TContext, TSelect, TInsert, TUpdate>;
343
+ interface CrudProcedures<TSelect, TInsert, TUpdate> {
344
+ list: AnyProcedure;
345
+ get: AnyProcedure;
346
+ create: AnyProcedure;
347
+ update: AnyProcedure;
348
+ delete: AnyProcedure;
349
+ deleteMany: AnyProcedure;
350
+ updateMany: AnyProcedure;
351
+ upsert: AnyProcedure;
352
+ }
326
353
  //#endregion
327
354
  //#region src/routers/_factory.d.ts
328
- declare function createCrudRouter<TContext = unknown>(config: CrudRouterConfig<TContext>): node_modules__trpc_server_dist_unstable_core_do_not_import_d_CjQPvBRI_mjs0.Router<{
329
- ctx: Context;
330
- meta: object;
331
- errorShape: _trpc_server5.TRPCDefaultErrorShape;
332
- transformer: true;
333
- }, _trpc_server5.TRPCDecorateCreateRouterOptions<{
334
- list: any;
335
- getById: any;
336
- create: any;
337
- update: any;
338
- delete: any;
339
- deleteMany: any;
340
- updateMany: any;
341
- }>> & _trpc_server5.TRPCDecorateCreateRouterOptions<{
342
- list: any;
343
- getById: any;
344
- create: any;
345
- update: any;
346
- delete: any;
347
- deleteMany: any;
348
- updateMany: any;
349
- }> & {
350
- procedures: {
351
- list: any;
352
- getById: any;
353
- create: any;
354
- update: any;
355
- delete: any;
356
- deleteMany: any;
357
- updateMany: any;
358
- };
355
+ /**
356
+ * 创建 CRUD Router
357
+ *
358
+ * @typeParam TContext - 上下文类型(包含 db)
359
+ * @typeParam TSelect - 查询返回类型(从 selectSchema 或 insertSchema 推断)
360
+ * @typeParam TInsert - 创建输入类型(从 insertSchema 推断)
361
+ * @typeParam TUpdate - 更新输入类型(从 updateSchema 推断)
362
+ */
363
+ declare function createCrudRouter<TContext = unknown, TSelect = unknown, TInsert = unknown, TUpdate = unknown>(config: CrudRouterConfig<TContext, TSelect, TInsert, TUpdate>): ReturnType<typeof router> & {
364
+ procedures: CrudProcedures<TSelect, TInsert, TUpdate>;
359
365
  };
360
366
  //#endregion
361
367
  //#region src/lib/middleware-helpers.d.ts
362
- type NextFn<TInput, TResult> = (input?: TInput) => Promise<TResult>;
363
- interface MiddlewareParams<TContext, TInput, TResult> {
364
- ctx: TContext;
365
- input?: TInput;
366
- next: NextFn<TInput, TResult>;
367
- }
368
+ /**
369
+ * Middleware 便捷工具函数
370
+ * 简化常见场景的 middleware 创建
371
+ */
368
372
  /**
369
373
  * 创建 after-only 的 middleware(最常见场景)
370
374
  * 在操作完成后执行副作用,不修改返回值
@@ -377,29 +381,10 @@ interface MiddlewareParams<TContext, TInput, TResult> {
377
381
  * }),
378
382
  * }
379
383
  */
380
- declare function afterMiddleware<TContext, TResult>(fn: (ctx: TContext, result: TResult) => void | Promise<void>): ({
381
- ctx,
382
- next
383
- }: {
384
- ctx: TContext;
385
- next: () => Promise<TResult>;
386
- }) => Promise<TResult>;
387
- /**
388
- * 创建 after-only 的 middleware,可修改返回值
389
- *
390
- * @example
391
- * middleware: {
392
- * get: afterMiddlewareTransform(async (ctx, result) => {
393
- * return { ...result, computed: calculateSomething(result) };
394
- * }),
395
- * }
396
- */
397
- declare function afterMiddlewareTransform<TContext, TResult>(fn: (ctx: TContext, result: TResult) => TResult | Promise<TResult>): ({
398
- ctx,
399
- next
400
- }: {
384
+ declare function afterMiddleware<TContext, TResult>(fn: (ctx: TContext, result: TResult) => void | Promise<void>): (params: {
401
385
  ctx: TContext;
402
386
  next: () => Promise<TResult>;
387
+ [key: string]: any;
403
388
  }) => Promise<TResult>;
404
389
  /**
405
390
  * 创建 before-only 的 middleware
@@ -412,11 +397,17 @@ declare function afterMiddlewareTransform<TContext, TResult>(fn: (ctx: TContext,
412
397
  * }),
413
398
  * }
414
399
  */
415
- declare function beforeMiddleware<TContext, TInput>(fn: (ctx: TContext, input: TInput) => TInput | Promise<TInput>): <TResult>({
416
- ctx,
417
- input,
418
- next
419
- }: MiddlewareParams<TContext, TInput, TResult>) => Promise<TResult>;
400
+ declare function beforeMiddleware<TContext, TInput>(fn: (ctx: TContext, input: TInput) => TInput | Promise<TInput>): <TResult>(params: {
401
+ ctx: TContext;
402
+ input: TInput;
403
+ next: (input?: TInput) => Promise<TResult>;
404
+ [key: string]: any;
405
+ }) => Promise<TResult>;
406
+ type AnyMiddlewareParams<TContext> = {
407
+ ctx: TContext;
408
+ next: (...args: any[]) => Promise<any>;
409
+ [key: string]: any;
410
+ };
420
411
  /**
421
412
  * 组合多个 middleware 为一个
422
413
  *
@@ -428,71 +419,15 @@ declare function beforeMiddleware<TContext, TInput>(fn: (ctx: TContext, input: T
428
419
  * ),
429
420
  * }
430
421
  */
431
- declare function composeMiddleware<TContext, TInput, TResult>(...middlewares: Array<(params: MiddlewareParams<TContext, TInput, TResult>) => Promise<TResult>>): (params: MiddlewareParams<TContext, TInput, TResult>) => Promise<TResult>;
432
- /**
433
- * 创建 list 操作的 after middleware
434
- */
435
- declare function afterList<TContext, TSelect>(fn: (ctx: TContext, result: ListResult<TSelect>) => void | Promise<void>): ({
436
- ctx,
437
- next
438
- }: {
439
- ctx: TContext;
440
- next: () => Promise<ListResult<TSelect>>;
441
- }) => Promise<ListResult<TSelect>>;
442
- /**
443
- * 创建 list 操作的 before middleware
444
- */
445
- declare function beforeList<TContext>(fn: (ctx: TContext, input: ListInput) => ListInput | Promise<ListInput>): <TResult>({
446
- ctx,
447
- input,
448
- next
449
- }: MiddlewareParams<TContext, ListInput, TResult>) => Promise<TResult>;
450
- /**
451
- * 创建 create 操作的 after middleware
452
- */
453
- declare function afterCreate<TContext, TSelect>(fn: (ctx: TContext, created: TSelect) => void | Promise<void>): ({
454
- ctx,
455
- next
456
- }: {
457
- ctx: TContext;
458
- next: () => Promise<TSelect>;
459
- }) => Promise<TSelect>;
460
- /**
461
- * 创建 create 操作的 before middleware
462
- */
463
- declare function beforeCreate<TContext, TInsert>(fn: (ctx: TContext, input: TInsert) => TInsert | Promise<TInsert>): <TResult>({
464
- ctx,
465
- input,
466
- next
467
- }: MiddlewareParams<TContext, TInsert, TResult>) => Promise<TResult>;
468
- /**
469
- * 创建 update 操作的 after middleware
470
- */
471
- declare function afterUpdate<TContext, TSelect>(fn: (ctx: TContext, updated: TSelect) => void | Promise<void>): ({
472
- ctx,
473
- next
474
- }: {
475
- ctx: TContext;
476
- next: () => Promise<TSelect>;
477
- }) => Promise<TSelect>;
478
- /**
479
- * 创建 delete 操作的 after middleware
480
- */
481
- declare function afterDelete<TContext, TSelect>(fn: (ctx: TContext, deleted: TSelect) => void | Promise<void>): ({
482
- ctx,
483
- next
484
- }: {
485
- ctx: TContext;
486
- next: () => Promise<TSelect>;
487
- }) => Promise<TSelect>;
422
+ declare function composeMiddleware<TContext, TResult>(...middlewares: Array<(params: AnyMiddlewareParams<TContext>) => Promise<any>>): (params: AnyMiddlewareParams<TContext>) => Promise<TResult>;
488
423
  //#endregion
489
424
  //#region src/routers/index.d.ts
490
- declare const appRouter: _trpc_server5.TRPCBuiltRouter<{
425
+ declare const appRouter: _trpc_server2.TRPCBuiltRouter<{
491
426
  ctx: Context;
492
427
  meta: object;
493
- errorShape: _trpc_server5.TRPCDefaultErrorShape;
428
+ errorShape: _trpc_server2.TRPCDefaultErrorShape;
494
429
  transformer: true;
495
- }, _trpc_server5.TRPCDecorateCreateRouterOptions<{}>>;
430
+ }, _trpc_server2.TRPCDecorateCreateRouterOptions<{}>>;
496
431
  type AppRouter = typeof appRouter;
497
432
  //#endregion
498
- export { type AnyProcedure, type AppRouter, type CrudMiddleware, type CrudOperation, type CrudRouterConfig, type DeclarativeConfig, type FactoryConfig, type ListInput, type ListResult, type ProceduresConfig, type SoftDeleteConfig, type SoftDeleteOption, type WriteOperation, afterCreate, afterDelete, afterList, afterMiddleware, afterMiddlewareTransform, afterUpdate, appRouter, beforeCreate, beforeList, beforeMiddleware, composeMiddleware, createCrudRouter, publicProcedure, router };
433
+ export { type AnyProcedure, type AppRouter, type CreateMiddlewareParams, type CrudMiddleware, type CrudOperation, type CrudProcedures, type CrudRouterConfig, type CrudRouterConfigBase, type DeclarativeConfig, type DeleteManyMiddlewareParams, type DeleteMiddlewareParams, type FactoryConfig, type GetMiddlewareParams, type ListInput, type ListMiddlewareParams, type ListResult, type ProceduresConfig, type SoftDeleteConfig, type SoftDeleteOption, type UpdateManyMiddlewareParams, type UpdateMiddlewareParams, type UpsertMiddlewareParams, type WriteOperation, afterMiddleware, appRouter, beforeMiddleware, composeMiddleware, createCrudRouter, publicProcedure, router };
package/dist/index.d.ts CHANGED
@@ -1,9 +1,8 @@
1
1
  import { z } from "zod";
2
2
  import { SQL } from "drizzle-orm";
3
- import * as _trpc_server5 from "@trpc/server";
3
+ import * as _trpc_server2 from "@trpc/server";
4
4
  import superjson from "superjson";
5
5
  import { PostgresJsDatabase } from "drizzle-orm/postgres-js";
6
- import * as node_modules__trpc_server_dist_unstable_core_do_not_import_d_CjQPvBRI_mjs0 from "node_modules/@trpc/server/dist/unstable-core-do-not-import.d-CjQPvBRI.mjs";
7
6
  import { PgTable } from "drizzle-orm/pg-core";
8
7
 
9
8
  //#region src/trpc.d.ts
@@ -14,16 +13,16 @@ import { PgTable } from "drizzle-orm/pg-core";
14
13
  interface Context {
15
14
  db: PostgresJsDatabase<Record<string, never>>;
16
15
  }
17
- declare const router: _trpc_server5.TRPCRouterBuilder<{
16
+ declare const router: _trpc_server2.TRPCRouterBuilder<{
18
17
  ctx: Context;
19
18
  meta: object;
20
- errorShape: _trpc_server5.TRPCDefaultErrorShape;
19
+ errorShape: _trpc_server2.TRPCDefaultErrorShape;
21
20
  transformer: true;
22
21
  }>;
23
- declare const publicProcedure: _trpc_server5.TRPCProcedureBuilder<Context, object, object, _trpc_server5.TRPCUnsetMarker, _trpc_server5.TRPCUnsetMarker, _trpc_server5.TRPCUnsetMarker, _trpc_server5.TRPCUnsetMarker, false>;
22
+ declare const publicProcedure: _trpc_server2.TRPCProcedureBuilder<Context, object, object, _trpc_server2.TRPCUnsetMarker, _trpc_server2.TRPCUnsetMarker, _trpc_server2.TRPCUnsetMarker, _trpc_server2.TRPCUnsetMarker, false>;
24
23
  //#endregion
25
24
  //#region src/types/config.d.ts
26
- type CrudOperation = "list" | "get" | "create" | "update" | "delete" | "deleteMany" | "updateMany";
25
+ type CrudOperation = "list" | "get" | "create" | "update" | "delete" | "deleteMany" | "updateMany" | "upsert";
27
26
  type WriteOperation = "create" | "update";
28
27
  type AnyProcedure = any;
29
28
  interface SoftDeleteConfig {
@@ -72,6 +71,57 @@ interface ListResult<TSelect> {
72
71
  perPage: number;
73
72
  pageCount: number;
74
73
  }
74
+ interface ListMiddlewareParams<TContext, TSelect> {
75
+ ctx: TContext;
76
+ input: ListInput;
77
+ next: (input?: ListInput) => Promise<ListResult<TSelect>>;
78
+ }
79
+ interface GetMiddlewareParams<TContext, TSelect> {
80
+ ctx: TContext;
81
+ id: string;
82
+ next: () => Promise<TSelect | null>;
83
+ }
84
+ interface CreateMiddlewareParams<TContext, TSelect, TInsert> {
85
+ ctx: TContext;
86
+ input: TInsert;
87
+ next: (input?: TInsert) => Promise<TSelect>;
88
+ }
89
+ interface UpdateMiddlewareParams<TContext, TSelect, TUpdate> {
90
+ ctx: TContext;
91
+ id: string;
92
+ data: TUpdate;
93
+ existing: TSelect;
94
+ next: (data?: TUpdate) => Promise<TSelect>;
95
+ }
96
+ interface DeleteMiddlewareParams<TContext, TSelect> {
97
+ ctx: TContext;
98
+ id: string;
99
+ existing: TSelect;
100
+ next: () => Promise<TSelect>;
101
+ }
102
+ interface DeleteManyMiddlewareParams<TContext> {
103
+ ctx: TContext;
104
+ ids: string[];
105
+ next: () => Promise<{
106
+ deleted: number;
107
+ }>;
108
+ }
109
+ interface UpdateManyMiddlewareParams<TContext, TUpdate> {
110
+ ctx: TContext;
111
+ ids: string[];
112
+ data: TUpdate;
113
+ next: (data?: TUpdate) => Promise<{
114
+ updated: number;
115
+ }>;
116
+ }
117
+ interface UpsertMiddlewareParams<TContext, TSelect, TInsert> {
118
+ ctx: TContext;
119
+ input: TInsert;
120
+ next: (input?: TInsert) => Promise<{
121
+ data: TSelect;
122
+ isNew: boolean;
123
+ }>;
124
+ }
75
125
  /**
76
126
  * 操作包装器 - 类似 tRPC middleware 的 next 模式
77
127
  *
@@ -82,6 +132,11 @@ interface ListResult<TSelect> {
82
132
  * - 条件拦截(不调用 next 直接返回/抛错)
83
133
  * - 包装事务(try/catch 包裹 next)
84
134
  *
135
+ * @typeParam TContext - 上下文类型
136
+ * @typeParam TSelect - 查询返回类型(从 selectSchema 推断)
137
+ * @typeParam TInsert - 创建输入类型(从 insertSchema 推断)
138
+ * @typeParam TUpdate - 更新输入类型(从 updateSchema 推断)
139
+ *
85
140
  * @example
86
141
  * // 修改输入 + 执行副作用
87
142
  * middleware: {
@@ -92,98 +147,60 @@ interface ListResult<TSelect> {
92
147
  * return result;
93
148
  * },
94
149
  * }
95
- *
96
- * @example
97
- * // 简单副作用 - 使用工具函数
98
- * import { afterMiddleware } from "@wordrhyme/auto-crud-server";
99
- *
100
- * middleware: {
101
- * create: afterMiddleware(async (ctx, result) => {
102
- * await sendEmail(result);
103
- * }),
104
- * }
105
150
  */
106
- interface CrudMiddleware<TContext = unknown> {
151
+ interface CrudMiddleware<TContext = unknown, TSelect = unknown, TInsert = unknown, TUpdate = unknown> {
107
152
  /**
108
153
  * 列表查询包装器
109
154
  */
110
- list?: (params: {
111
- ctx: TContext;
112
- input: ListInput;
113
- next: (input?: ListInput) => Promise<ListResult<unknown>>;
114
- }) => Promise<ListResult<unknown>>;
155
+ list?: (params: ListMiddlewareParams<TContext, TSelect>) => Promise<ListResult<TSelect>>;
115
156
  /**
116
157
  * 单条查询包装器
117
158
  */
118
- get?: (params: {
119
- ctx: TContext;
120
- id: string;
121
- next: () => Promise<unknown | null>;
122
- }) => Promise<unknown | null>;
159
+ get?: (params: GetMiddlewareParams<TContext, TSelect>) => Promise<TSelect | null>;
123
160
  /**
124
161
  * 创建包装器
125
162
  */
126
- create?: (params: {
127
- ctx: TContext;
128
- input: unknown;
129
- next: (input?: unknown) => Promise<unknown>;
130
- }) => Promise<unknown>;
163
+ create?: (params: CreateMiddlewareParams<TContext, TSelect, TInsert>) => Promise<TSelect>;
131
164
  /**
132
165
  * 更新包装器
133
166
  * existing: 更新前的记录
134
167
  */
135
- update?: (params: {
136
- ctx: TContext;
137
- id: string;
138
- data: unknown;
139
- existing: unknown;
140
- next: (data?: unknown) => Promise<unknown>;
141
- }) => Promise<unknown>;
168
+ update?: (params: UpdateMiddlewareParams<TContext, TSelect, TUpdate>) => Promise<TSelect>;
142
169
  /**
143
170
  * 删除包装器
144
171
  * existing: 删除前的记录
145
172
  */
146
- delete?: (params: {
147
- ctx: TContext;
148
- id: string;
149
- existing: unknown;
150
- next: () => Promise<unknown>;
151
- }) => Promise<unknown>;
173
+ delete?: (params: DeleteMiddlewareParams<TContext, TSelect>) => Promise<TSelect>;
152
174
  /**
153
175
  * 批量删除包装器
154
176
  */
155
- deleteMany?: (params: {
156
- ctx: TContext;
157
- ids: string[];
158
- next: () => Promise<{
159
- deleted: number;
160
- }>;
161
- }) => Promise<{
177
+ deleteMany?: (params: DeleteManyMiddlewareParams<TContext>) => Promise<{
162
178
  deleted: number;
163
179
  }>;
164
180
  /**
165
181
  * 批量更新包装器
166
182
  */
167
- updateMany?: (params: {
168
- ctx: TContext;
169
- ids: string[];
170
- data: unknown;
171
- next: (data?: unknown) => Promise<{
172
- updated: number;
173
- }>;
174
- }) => Promise<{
183
+ updateMany?: (params: UpdateManyMiddlewareParams<TContext, TUpdate>) => Promise<{
175
184
  updated: number;
176
185
  }>;
186
+ /**
187
+ * Upsert 包装器
188
+ * isNew: 是否为新建(冲突检测后)
189
+ */
190
+ upsert?: (params: UpsertMiddlewareParams<TContext, TSelect, TInsert>) => Promise<{
191
+ data: TSelect;
192
+ isNew: boolean;
193
+ }>;
177
194
  }
178
- interface CrudRouterConfigBase<TContext = unknown> {
195
+ interface CrudRouterConfigBase<TContext = unknown, TSelect = unknown, TInsert = unknown, TUpdate = unknown> {
179
196
  /** Drizzle 表定义 */
180
197
  table: PgTable;
181
198
  /** 查询返回 Schema */
182
- selectSchema?: z.ZodType;
199
+ selectSchema?: z.ZodType<TSelect>;
183
200
  /** 创建输入 Schema */
184
- insertSchema: z.ZodType;
201
+ insertSchema: z.ZodType<TInsert>;
185
202
  /** 更新输入 Schema */
186
- updateSchema: z.ZodType;
203
+ updateSchema: z.ZodType<TUpdate>;
187
204
  /** ID 字段名,默认 "id" */
188
205
  idField?: string;
189
206
  /** 可过滤的列白名单 */
@@ -238,9 +255,9 @@ interface CrudRouterConfigBase<TContext = unknown> {
238
255
  * }),
239
256
  * }
240
257
  */
241
- middleware?: CrudMiddleware<TContext>;
258
+ middleware?: CrudMiddleware<TContext, TSelect, TInsert, TUpdate>;
242
259
  }
243
- interface DeclarativeConfig<TContext = unknown> extends CrudRouterConfigBase<TContext> {
260
+ interface DeclarativeConfig<TContext = unknown, TSelect = unknown, TInsert = unknown, TUpdate = unknown> extends CrudRouterConfigBase<TContext, TSelect, TInsert, TUpdate> {
244
261
  mode?: "declarative";
245
262
  /**
246
263
  * 基础 procedure(认证)
@@ -278,9 +295,9 @@ interface DeclarativeConfig<TContext = unknown> extends CrudRouterConfigBase<TCo
278
295
  * @example
279
296
  * authorize: (ctx, resource, op) => resource.ownerId === ctx.user.id
280
297
  */
281
- authorize?: (ctx: TContext, resource: Record<string, unknown>, operation: CrudOperation) => boolean | Promise<boolean>;
298
+ authorize?: (ctx: TContext, resource: TSelect, operation: CrudOperation) => boolean | Promise<boolean>;
282
299
  }
283
- interface FactoryConfig<TContext = unknown> extends CrudRouterConfigBase<TContext> {
300
+ interface FactoryConfig<TContext = unknown, TSelect = unknown, TInsert = unknown, TUpdate = unknown> extends CrudRouterConfigBase<TContext, TSelect, TInsert, TUpdate> {
284
301
  mode: "factory";
285
302
  /**
286
303
  * Procedure 工厂函数
@@ -299,7 +316,7 @@ interface FactoryConfig<TContext = unknown> extends CrudRouterConfigBase<TContex
299
316
  */
300
317
  inject?: (ctx: TContext, operation: WriteOperation) => Record<string, unknown>;
301
318
  }
302
- interface ProceduresConfig<TContext = unknown> extends CrudRouterConfigBase<TContext> {
319
+ interface ProceduresConfig<TContext = unknown, TSelect = unknown, TInsert = unknown, TUpdate = unknown> extends CrudRouterConfigBase<TContext, TSelect, TInsert, TUpdate> {
303
320
  mode: "procedures";
304
321
  /**
305
322
  * 按操作指定不同的 procedure
@@ -323,49 +340,36 @@ interface ProceduresConfig<TContext = unknown> extends CrudRouterConfigBase<TCon
323
340
  */
324
341
  inject?: (ctx: TContext, operation: WriteOperation) => Record<string, unknown>;
325
342
  }
326
- type CrudRouterConfig<TContext = unknown> = DeclarativeConfig<TContext> | FactoryConfig<TContext> | ProceduresConfig<TContext>;
343
+ type CrudRouterConfig<TContext = unknown, TSelect = unknown, TInsert = unknown, TUpdate = unknown> = DeclarativeConfig<TContext, TSelect, TInsert, TUpdate> | FactoryConfig<TContext, TSelect, TInsert, TUpdate> | ProceduresConfig<TContext, TSelect, TInsert, TUpdate>;
344
+ interface CrudProcedures<TSelect, TInsert, TUpdate> {
345
+ list: AnyProcedure;
346
+ get: AnyProcedure;
347
+ create: AnyProcedure;
348
+ update: AnyProcedure;
349
+ delete: AnyProcedure;
350
+ deleteMany: AnyProcedure;
351
+ updateMany: AnyProcedure;
352
+ upsert: AnyProcedure;
353
+ }
327
354
  //#endregion
328
355
  //#region src/routers/_factory.d.ts
329
- declare function createCrudRouter<TContext = unknown>(config: CrudRouterConfig<TContext>): node_modules__trpc_server_dist_unstable_core_do_not_import_d_CjQPvBRI_mjs0.Router<{
330
- ctx: Context;
331
- meta: object;
332
- errorShape: _trpc_server5.TRPCDefaultErrorShape;
333
- transformer: true;
334
- }, _trpc_server5.TRPCDecorateCreateRouterOptions<{
335
- list: any;
336
- getById: any;
337
- create: any;
338
- update: any;
339
- delete: any;
340
- deleteMany: any;
341
- updateMany: any;
342
- }>> & _trpc_server5.TRPCDecorateCreateRouterOptions<{
343
- list: any;
344
- getById: any;
345
- create: any;
346
- update: any;
347
- delete: any;
348
- deleteMany: any;
349
- updateMany: any;
350
- }> & {
351
- procedures: {
352
- list: any;
353
- getById: any;
354
- create: any;
355
- update: any;
356
- delete: any;
357
- deleteMany: any;
358
- updateMany: any;
359
- };
356
+ /**
357
+ * 创建 CRUD Router
358
+ *
359
+ * @typeParam TContext - 上下文类型(包含 db)
360
+ * @typeParam TSelect - 查询返回类型(从 selectSchema 或 insertSchema 推断)
361
+ * @typeParam TInsert - 创建输入类型(从 insertSchema 推断)
362
+ * @typeParam TUpdate - 更新输入类型(从 updateSchema 推断)
363
+ */
364
+ declare function createCrudRouter<TContext = unknown, TSelect = unknown, TInsert = unknown, TUpdate = unknown>(config: CrudRouterConfig<TContext, TSelect, TInsert, TUpdate>): ReturnType<typeof router> & {
365
+ procedures: CrudProcedures<TSelect, TInsert, TUpdate>;
360
366
  };
361
367
  //#endregion
362
368
  //#region src/lib/middleware-helpers.d.ts
363
- type NextFn<TInput, TResult> = (input?: TInput) => Promise<TResult>;
364
- interface MiddlewareParams<TContext, TInput, TResult> {
365
- ctx: TContext;
366
- input?: TInput;
367
- next: NextFn<TInput, TResult>;
368
- }
369
+ /**
370
+ * Middleware 便捷工具函数
371
+ * 简化常见场景的 middleware 创建
372
+ */
369
373
  /**
370
374
  * 创建 after-only 的 middleware(最常见场景)
371
375
  * 在操作完成后执行副作用,不修改返回值
@@ -378,29 +382,10 @@ interface MiddlewareParams<TContext, TInput, TResult> {
378
382
  * }),
379
383
  * }
380
384
  */
381
- declare function afterMiddleware<TContext, TResult>(fn: (ctx: TContext, result: TResult) => void | Promise<void>): ({
382
- ctx,
383
- next
384
- }: {
385
- ctx: TContext;
386
- next: () => Promise<TResult>;
387
- }) => Promise<TResult>;
388
- /**
389
- * 创建 after-only 的 middleware,可修改返回值
390
- *
391
- * @example
392
- * middleware: {
393
- * get: afterMiddlewareTransform(async (ctx, result) => {
394
- * return { ...result, computed: calculateSomething(result) };
395
- * }),
396
- * }
397
- */
398
- declare function afterMiddlewareTransform<TContext, TResult>(fn: (ctx: TContext, result: TResult) => TResult | Promise<TResult>): ({
399
- ctx,
400
- next
401
- }: {
385
+ declare function afterMiddleware<TContext, TResult>(fn: (ctx: TContext, result: TResult) => void | Promise<void>): (params: {
402
386
  ctx: TContext;
403
387
  next: () => Promise<TResult>;
388
+ [key: string]: any;
404
389
  }) => Promise<TResult>;
405
390
  /**
406
391
  * 创建 before-only 的 middleware
@@ -413,11 +398,17 @@ declare function afterMiddlewareTransform<TContext, TResult>(fn: (ctx: TContext,
413
398
  * }),
414
399
  * }
415
400
  */
416
- declare function beforeMiddleware<TContext, TInput>(fn: (ctx: TContext, input: TInput) => TInput | Promise<TInput>): <TResult>({
417
- ctx,
418
- input,
419
- next
420
- }: MiddlewareParams<TContext, TInput, TResult>) => Promise<TResult>;
401
+ declare function beforeMiddleware<TContext, TInput>(fn: (ctx: TContext, input: TInput) => TInput | Promise<TInput>): <TResult>(params: {
402
+ ctx: TContext;
403
+ input: TInput;
404
+ next: (input?: TInput) => Promise<TResult>;
405
+ [key: string]: any;
406
+ }) => Promise<TResult>;
407
+ type AnyMiddlewareParams<TContext> = {
408
+ ctx: TContext;
409
+ next: (...args: any[]) => Promise<any>;
410
+ [key: string]: any;
411
+ };
421
412
  /**
422
413
  * 组合多个 middleware 为一个
423
414
  *
@@ -429,71 +420,15 @@ declare function beforeMiddleware<TContext, TInput>(fn: (ctx: TContext, input: T
429
420
  * ),
430
421
  * }
431
422
  */
432
- declare function composeMiddleware<TContext, TInput, TResult>(...middlewares: Array<(params: MiddlewareParams<TContext, TInput, TResult>) => Promise<TResult>>): (params: MiddlewareParams<TContext, TInput, TResult>) => Promise<TResult>;
433
- /**
434
- * 创建 list 操作的 after middleware
435
- */
436
- declare function afterList<TContext, TSelect>(fn: (ctx: TContext, result: ListResult<TSelect>) => void | Promise<void>): ({
437
- ctx,
438
- next
439
- }: {
440
- ctx: TContext;
441
- next: () => Promise<ListResult<TSelect>>;
442
- }) => Promise<ListResult<TSelect>>;
443
- /**
444
- * 创建 list 操作的 before middleware
445
- */
446
- declare function beforeList<TContext>(fn: (ctx: TContext, input: ListInput) => ListInput | Promise<ListInput>): <TResult>({
447
- ctx,
448
- input,
449
- next
450
- }: MiddlewareParams<TContext, ListInput, TResult>) => Promise<TResult>;
451
- /**
452
- * 创建 create 操作的 after middleware
453
- */
454
- declare function afterCreate<TContext, TSelect>(fn: (ctx: TContext, created: TSelect) => void | Promise<void>): ({
455
- ctx,
456
- next
457
- }: {
458
- ctx: TContext;
459
- next: () => Promise<TSelect>;
460
- }) => Promise<TSelect>;
461
- /**
462
- * 创建 create 操作的 before middleware
463
- */
464
- declare function beforeCreate<TContext, TInsert>(fn: (ctx: TContext, input: TInsert) => TInsert | Promise<TInsert>): <TResult>({
465
- ctx,
466
- input,
467
- next
468
- }: MiddlewareParams<TContext, TInsert, TResult>) => Promise<TResult>;
469
- /**
470
- * 创建 update 操作的 after middleware
471
- */
472
- declare function afterUpdate<TContext, TSelect>(fn: (ctx: TContext, updated: TSelect) => void | Promise<void>): ({
473
- ctx,
474
- next
475
- }: {
476
- ctx: TContext;
477
- next: () => Promise<TSelect>;
478
- }) => Promise<TSelect>;
479
- /**
480
- * 创建 delete 操作的 after middleware
481
- */
482
- declare function afterDelete<TContext, TSelect>(fn: (ctx: TContext, deleted: TSelect) => void | Promise<void>): ({
483
- ctx,
484
- next
485
- }: {
486
- ctx: TContext;
487
- next: () => Promise<TSelect>;
488
- }) => Promise<TSelect>;
423
+ declare function composeMiddleware<TContext, TResult>(...middlewares: Array<(params: AnyMiddlewareParams<TContext>) => Promise<any>>): (params: AnyMiddlewareParams<TContext>) => Promise<TResult>;
489
424
  //#endregion
490
425
  //#region src/routers/index.d.ts
491
- declare const appRouter: _trpc_server5.TRPCBuiltRouter<{
426
+ declare const appRouter: _trpc_server2.TRPCBuiltRouter<{
492
427
  ctx: Context;
493
428
  meta: object;
494
- errorShape: _trpc_server5.TRPCDefaultErrorShape;
429
+ errorShape: _trpc_server2.TRPCDefaultErrorShape;
495
430
  transformer: true;
496
- }, _trpc_server5.TRPCDecorateCreateRouterOptions<{}>>;
431
+ }, _trpc_server2.TRPCDecorateCreateRouterOptions<{}>>;
497
432
  type AppRouter = typeof appRouter;
498
433
  //#endregion
499
- export { type AnyProcedure, type AppRouter, type CrudMiddleware, type CrudOperation, type CrudRouterConfig, type DeclarativeConfig, type FactoryConfig, type ListInput, type ListResult, type ProceduresConfig, type SoftDeleteConfig, type SoftDeleteOption, type WriteOperation, afterCreate, afterDelete, afterList, afterMiddleware, afterMiddlewareTransform, afterUpdate, appRouter, beforeCreate, beforeList, beforeMiddleware, composeMiddleware, createCrudRouter, publicProcedure, router };
434
+ export { type AnyProcedure, type AppRouter, type CreateMiddlewareParams, type CrudMiddleware, type CrudOperation, type CrudProcedures, type CrudRouterConfig, type CrudRouterConfigBase, type DeclarativeConfig, type DeleteManyMiddlewareParams, type DeleteMiddlewareParams, type FactoryConfig, type GetMiddlewareParams, type ListInput, type ListMiddlewareParams, type ListResult, type ProceduresConfig, type SoftDeleteConfig, type SoftDeleteOption, type UpdateManyMiddlewareParams, type UpdateMiddlewareParams, type UpsertMiddlewareParams, type WriteOperation, afterMiddleware, appRouter, beforeMiddleware, composeMiddleware, createCrudRouter, publicProcedure, router };
package/dist/index.js CHANGED
@@ -446,6 +446,14 @@ function validateColumn(columnId, allowedColumns) {
446
446
  if (!allowedColumns || allowedColumns.length === 0) return true;
447
447
  return allowedColumns.includes(columnId);
448
448
  }
449
+ /**
450
+ * 创建 CRUD Router
451
+ *
452
+ * @typeParam TContext - 上下文类型(包含 db)
453
+ * @typeParam TSelect - 查询返回类型(从 selectSchema 或 insertSchema 推断)
454
+ * @typeParam TInsert - 创建输入类型(从 insertSchema 推断)
455
+ * @typeParam TUpdate - 更新输入类型(从 updateSchema 推断)
456
+ */
449
457
  function createCrudRouter(config) {
450
458
  const { table, insertSchema, updateSchema, idField = "id", filterableColumns, sortableColumns, maxBatchSize = 100, softDelete: softDeleteOption, middleware = {} } = config;
451
459
  const resolved = resolveConfig(config);
@@ -468,6 +476,12 @@ function createCrudRouter(config) {
468
476
  };
469
477
  const withGuard = async (ctx, operation) => {
470
478
  if (resolved.guard) {
479
+ if (operation === "upsert") {
480
+ const canCreate = await resolved.guard(ctx, "create");
481
+ const canUpdate = await resolved.guard(ctx, "update");
482
+ if (!canCreate || !canUpdate) throw new Error("Forbidden: upsert requires both create and update permissions");
483
+ return;
484
+ }
471
485
  if (!await resolved.guard(ctx, operation)) throw new Error(`Forbidden: ${operation} not allowed`);
472
486
  }
473
487
  };
@@ -515,7 +529,7 @@ function createCrudRouter(config) {
515
529
  });
516
530
  return doList(listInput);
517
531
  }),
518
- getById: resolved.procedureFactory("get").input(z.string()).query(async ({ ctx, input }) => {
532
+ get: resolved.procedureFactory("get").input(z.string()).query(async ({ ctx, input }) => {
519
533
  await withGuard(ctx, "get");
520
534
  const doGet = async () => {
521
535
  const where = buildWhere(ctx, "get", eq(getIdColumn(), input));
@@ -630,6 +644,37 @@ function createCrudRouter(config) {
630
644
  next: async (modifiedData) => doUpdateMany(modifiedData ?? input.data)
631
645
  });
632
646
  return doUpdateMany(input.data);
647
+ }),
648
+ upsert: resolved.procedureFactory("upsert").input(insertSchema).mutation(async ({ ctx, input }) => {
649
+ await withGuard(ctx, "upsert");
650
+ const doUpsert = async (inputData) => {
651
+ const injectData = resolved.getInject(ctx, "create");
652
+ const data = {
653
+ ...inputData,
654
+ ...injectData
655
+ };
656
+ const inputId = inputData[idField];
657
+ let isNew = true;
658
+ if (inputId) {
659
+ const where = buildWhere(ctx, "get", eq(getIdColumn(), inputId));
660
+ const [existing] = await ctx.db.select().from(table).where(where);
661
+ isNew = !existing;
662
+ }
663
+ const [result] = await ctx.db.insert(table).values(data).onConflictDoUpdate({
664
+ target: getIdColumn(),
665
+ set: data
666
+ }).returning();
667
+ return {
668
+ data: result,
669
+ isNew
670
+ };
671
+ };
672
+ if (m.upsert) return m.upsert({
673
+ ctx,
674
+ input,
675
+ next: async (modifiedInput) => doUpsert(modifiedInput ?? input)
676
+ });
677
+ return doUpsert(input);
633
678
  })
634
679
  };
635
680
  const crudRouter = router(procedures);
@@ -639,6 +684,10 @@ function createCrudRouter(config) {
639
684
  //#endregion
640
685
  //#region src/lib/middleware-helpers.ts
641
686
  /**
687
+ * Middleware 便捷工具函数
688
+ * 简化常见场景的 middleware 创建
689
+ */
690
+ /**
642
691
  * 创建 after-only 的 middleware(最常见场景)
643
692
  * 在操作完成后执行副作用,不修改返回值
644
693
  *
@@ -651,28 +700,13 @@ function createCrudRouter(config) {
651
700
  * }
652
701
  */
653
702
  function afterMiddleware(fn) {
654
- return async ({ ctx, next }) => {
655
- const result = await next();
656
- await fn(ctx, result);
703
+ return async (params) => {
704
+ const result = await params.next();
705
+ await fn(params.ctx, result);
657
706
  return result;
658
707
  };
659
708
  }
660
709
  /**
661
- * 创建 after-only 的 middleware,可修改返回值
662
- *
663
- * @example
664
- * middleware: {
665
- * get: afterMiddlewareTransform(async (ctx, result) => {
666
- * return { ...result, computed: calculateSomething(result) };
667
- * }),
668
- * }
669
- */
670
- function afterMiddlewareTransform(fn) {
671
- return async ({ ctx, next }) => {
672
- return fn(ctx, await next());
673
- };
674
- }
675
- /**
676
710
  * 创建 before-only 的 middleware
677
711
  * 在操作执行前修改输入数据
678
712
  *
@@ -684,8 +718,9 @@ function afterMiddlewareTransform(fn) {
684
718
  * }
685
719
  */
686
720
  function beforeMiddleware(fn) {
687
- return async ({ ctx, input, next }) => {
688
- return next(await fn(ctx, input));
721
+ return async (params) => {
722
+ const modifiedInput = await fn(params.ctx, params.input);
723
+ return params.next(modifiedInput);
689
724
  };
690
725
  }
691
726
  /**
@@ -702,58 +737,21 @@ function beforeMiddleware(fn) {
702
737
  function composeMiddleware(...middlewares) {
703
738
  return async (params) => {
704
739
  let index = 0;
705
- const executeNext = async (input) => {
706
- if (index >= middlewares.length) return params.next(input);
740
+ const executeNext = async (...args) => {
741
+ if (index >= middlewares.length) return params.next(...args);
707
742
  const currentMiddleware = middlewares[index++];
708
743
  return currentMiddleware({
709
- ctx: params.ctx,
710
- input,
744
+ ...params,
711
745
  next: executeNext
712
746
  });
713
747
  };
714
- return executeNext(params.input);
748
+ return executeNext();
715
749
  };
716
750
  }
717
- /**
718
- * 创建 list 操作的 after middleware
719
- */
720
- function afterList(fn) {
721
- return afterMiddleware(fn);
722
- }
723
- /**
724
- * 创建 list 操作的 before middleware
725
- */
726
- function beforeList(fn) {
727
- return beforeMiddleware(fn);
728
- }
729
- /**
730
- * 创建 create 操作的 after middleware
731
- */
732
- function afterCreate(fn) {
733
- return afterMiddleware(fn);
734
- }
735
- /**
736
- * 创建 create 操作的 before middleware
737
- */
738
- function beforeCreate(fn) {
739
- return beforeMiddleware(fn);
740
- }
741
- /**
742
- * 创建 update 操作的 after middleware
743
- */
744
- function afterUpdate(fn) {
745
- return afterMiddleware(fn);
746
- }
747
- /**
748
- * 创建 delete 操作的 after middleware
749
- */
750
- function afterDelete(fn) {
751
- return afterMiddleware(fn);
752
- }
753
751
 
754
752
  //#endregion
755
753
  //#region src/routers/index.ts
756
754
  const appRouter = router({});
757
755
 
758
756
  //#endregion
759
- export { afterCreate, afterDelete, afterList, afterMiddleware, afterMiddlewareTransform, afterUpdate, appRouter, beforeCreate, beforeList, beforeMiddleware, composeMiddleware, createCrudRouter, publicProcedure, router };
757
+ export { afterMiddleware, appRouter, beforeMiddleware, composeMiddleware, createCrudRouter, publicProcedure, router };
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@wordrhyme/auto-crud-server",
3
3
  "type": "module",
4
- "version": "0.6.0",
4
+ "version": "0.6.1",
5
5
  "description": "tRPC server utilities for auto-crud - automatic CRUD routers for Drizzle ORM",
6
6
  "author": "wordrhyme",
7
7
  "license": "MIT",
@@ -49,8 +49,8 @@
49
49
  "@internal/eslint-config": "0.3.0",
50
50
  "@internal/prettier-config": "0.0.1",
51
51
  "@internal/tsconfig": "0.1.0",
52
- "@internal/tsdown-config": "0.1.0",
53
- "@internal/vitest-config": "0.1.0"
52
+ "@internal/vitest-config": "0.1.0",
53
+ "@internal/tsdown-config": "0.1.0"
54
54
  },
55
55
  "prettier": "@internal/prettier-config",
56
56
  "scripts": {