@wordrhyme/auto-crud-server 0.3.0 → 0.6.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 +124 -1
- package/dist/index.cjs +240 -55
- package/dist/index.d.cts +294 -5
- package/dist/index.d.ts +294 -5
- package/dist/index.js +231 -56
- package/package.json +1 -1
package/dist/index.d.cts
CHANGED
|
@@ -42,7 +42,139 @@ interface SoftDeleteConfig {
|
|
|
42
42
|
* - SoftDeleteConfig: 完整配置
|
|
43
43
|
*/
|
|
44
44
|
type SoftDeleteOption = string | boolean | SoftDeleteConfig;
|
|
45
|
-
|
|
45
|
+
/**
|
|
46
|
+
* 列表查询输入类型
|
|
47
|
+
*/
|
|
48
|
+
interface ListInput {
|
|
49
|
+
page: number;
|
|
50
|
+
perPage: number;
|
|
51
|
+
sort?: Array<{
|
|
52
|
+
id: string;
|
|
53
|
+
desc: boolean;
|
|
54
|
+
}>;
|
|
55
|
+
filters?: Array<{
|
|
56
|
+
id: string;
|
|
57
|
+
value: string | string[];
|
|
58
|
+
variant: string;
|
|
59
|
+
operator: string;
|
|
60
|
+
filterId: string;
|
|
61
|
+
}>;
|
|
62
|
+
joinOperator: "and" | "or";
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* 列表查询结果类型
|
|
66
|
+
*/
|
|
67
|
+
interface ListResult<TSelect> {
|
|
68
|
+
data: TSelect[];
|
|
69
|
+
total: number;
|
|
70
|
+
page: number;
|
|
71
|
+
perPage: number;
|
|
72
|
+
pageCount: number;
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* 操作包装器 - 类似 tRPC middleware 的 next 模式
|
|
76
|
+
*
|
|
77
|
+
* 功能:
|
|
78
|
+
* - 修改输入数据(传参给 next)
|
|
79
|
+
* - 修改返回值(处理 next 的结果)
|
|
80
|
+
* - 执行副作用(日志、通知、审计)
|
|
81
|
+
* - 条件拦截(不调用 next 直接返回/抛错)
|
|
82
|
+
* - 包装事务(try/catch 包裹 next)
|
|
83
|
+
*
|
|
84
|
+
* @example
|
|
85
|
+
* // 修改输入 + 执行副作用
|
|
86
|
+
* middleware: {
|
|
87
|
+
* create: async ({ ctx, input, next }) => {
|
|
88
|
+
* const data = { ...input, slug: slugify(input.title) };
|
|
89
|
+
* const result = await next(data);
|
|
90
|
+
* await sendNotification(result);
|
|
91
|
+
* return result;
|
|
92
|
+
* },
|
|
93
|
+
* }
|
|
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
|
+
*/
|
|
105
|
+
interface CrudMiddleware<TContext = unknown> {
|
|
106
|
+
/**
|
|
107
|
+
* 列表查询包装器
|
|
108
|
+
*/
|
|
109
|
+
list?: (params: {
|
|
110
|
+
ctx: TContext;
|
|
111
|
+
input: ListInput;
|
|
112
|
+
next: (input?: ListInput) => Promise<ListResult<unknown>>;
|
|
113
|
+
}) => Promise<ListResult<unknown>>;
|
|
114
|
+
/**
|
|
115
|
+
* 单条查询包装器
|
|
116
|
+
*/
|
|
117
|
+
get?: (params: {
|
|
118
|
+
ctx: TContext;
|
|
119
|
+
id: string;
|
|
120
|
+
next: () => Promise<unknown | null>;
|
|
121
|
+
}) => Promise<unknown | null>;
|
|
122
|
+
/**
|
|
123
|
+
* 创建包装器
|
|
124
|
+
*/
|
|
125
|
+
create?: (params: {
|
|
126
|
+
ctx: TContext;
|
|
127
|
+
input: unknown;
|
|
128
|
+
next: (input?: unknown) => Promise<unknown>;
|
|
129
|
+
}) => Promise<unknown>;
|
|
130
|
+
/**
|
|
131
|
+
* 更新包装器
|
|
132
|
+
* existing: 更新前的记录
|
|
133
|
+
*/
|
|
134
|
+
update?: (params: {
|
|
135
|
+
ctx: TContext;
|
|
136
|
+
id: string;
|
|
137
|
+
data: unknown;
|
|
138
|
+
existing: unknown;
|
|
139
|
+
next: (data?: unknown) => Promise<unknown>;
|
|
140
|
+
}) => Promise<unknown>;
|
|
141
|
+
/**
|
|
142
|
+
* 删除包装器
|
|
143
|
+
* existing: 删除前的记录
|
|
144
|
+
*/
|
|
145
|
+
delete?: (params: {
|
|
146
|
+
ctx: TContext;
|
|
147
|
+
id: string;
|
|
148
|
+
existing: unknown;
|
|
149
|
+
next: () => Promise<unknown>;
|
|
150
|
+
}) => Promise<unknown>;
|
|
151
|
+
/**
|
|
152
|
+
* 批量删除包装器
|
|
153
|
+
*/
|
|
154
|
+
deleteMany?: (params: {
|
|
155
|
+
ctx: TContext;
|
|
156
|
+
ids: string[];
|
|
157
|
+
next: () => Promise<{
|
|
158
|
+
deleted: number;
|
|
159
|
+
}>;
|
|
160
|
+
}) => Promise<{
|
|
161
|
+
deleted: number;
|
|
162
|
+
}>;
|
|
163
|
+
/**
|
|
164
|
+
* 批量更新包装器
|
|
165
|
+
*/
|
|
166
|
+
updateMany?: (params: {
|
|
167
|
+
ctx: TContext;
|
|
168
|
+
ids: string[];
|
|
169
|
+
data: unknown;
|
|
170
|
+
next: (data?: unknown) => Promise<{
|
|
171
|
+
updated: number;
|
|
172
|
+
}>;
|
|
173
|
+
}) => Promise<{
|
|
174
|
+
updated: number;
|
|
175
|
+
}>;
|
|
176
|
+
}
|
|
177
|
+
interface CrudRouterConfigBase<TContext = unknown> {
|
|
46
178
|
/** Drizzle 表定义 */
|
|
47
179
|
table: PgTable;
|
|
48
180
|
/** 查询返回 Schema */
|
|
@@ -77,8 +209,37 @@ interface CrudRouterConfigBase {
|
|
|
77
209
|
* softDelete: { column: 'isDeleted', value: () => true }
|
|
78
210
|
*/
|
|
79
211
|
softDelete?: SoftDeleteOption;
|
|
212
|
+
/**
|
|
213
|
+
* 操作中间件
|
|
214
|
+
* 类似 tRPC middleware 的 next 模式,完全控制操作流程
|
|
215
|
+
*
|
|
216
|
+
* @example
|
|
217
|
+
* // 完整控制
|
|
218
|
+
* middleware: {
|
|
219
|
+
* create: async ({ ctx, input, next }) => {
|
|
220
|
+
* const data = { ...input, slug: slugify(input.title) };
|
|
221
|
+
* const result = await next(data);
|
|
222
|
+
* await sendNotification(result);
|
|
223
|
+
* return result;
|
|
224
|
+
* },
|
|
225
|
+
* }
|
|
226
|
+
*
|
|
227
|
+
* @example
|
|
228
|
+
* // 使用工具函数简化
|
|
229
|
+
* import { afterMiddleware, beforeMiddleware } from "@wordrhyme/auto-crud-server";
|
|
230
|
+
*
|
|
231
|
+
* middleware: {
|
|
232
|
+
* create: afterMiddleware(async (ctx, result) => {
|
|
233
|
+
* await sendEmail(result);
|
|
234
|
+
* }),
|
|
235
|
+
* update: beforeMiddleware(async (ctx, data) => {
|
|
236
|
+
* return { ...data, updatedAt: new Date() };
|
|
237
|
+
* }),
|
|
238
|
+
* }
|
|
239
|
+
*/
|
|
240
|
+
middleware?: CrudMiddleware<TContext>;
|
|
80
241
|
}
|
|
81
|
-
interface DeclarativeConfig<TContext = unknown> extends CrudRouterConfigBase {
|
|
242
|
+
interface DeclarativeConfig<TContext = unknown> extends CrudRouterConfigBase<TContext> {
|
|
82
243
|
mode?: "declarative";
|
|
83
244
|
/**
|
|
84
245
|
* 基础 procedure(认证)
|
|
@@ -118,7 +279,7 @@ interface DeclarativeConfig<TContext = unknown> extends CrudRouterConfigBase {
|
|
|
118
279
|
*/
|
|
119
280
|
authorize?: (ctx: TContext, resource: Record<string, unknown>, operation: CrudOperation) => boolean | Promise<boolean>;
|
|
120
281
|
}
|
|
121
|
-
interface FactoryConfig<TContext = unknown> extends CrudRouterConfigBase {
|
|
282
|
+
interface FactoryConfig<TContext = unknown> extends CrudRouterConfigBase<TContext> {
|
|
122
283
|
mode: "factory";
|
|
123
284
|
/**
|
|
124
285
|
* Procedure 工厂函数
|
|
@@ -137,7 +298,7 @@ interface FactoryConfig<TContext = unknown> extends CrudRouterConfigBase {
|
|
|
137
298
|
*/
|
|
138
299
|
inject?: (ctx: TContext, operation: WriteOperation) => Record<string, unknown>;
|
|
139
300
|
}
|
|
140
|
-
interface ProceduresConfig<TContext = unknown> extends CrudRouterConfigBase {
|
|
301
|
+
interface ProceduresConfig<TContext = unknown> extends CrudRouterConfigBase<TContext> {
|
|
141
302
|
mode: "procedures";
|
|
142
303
|
/**
|
|
143
304
|
* 按操作指定不同的 procedure
|
|
@@ -197,6 +358,134 @@ declare function createCrudRouter<TContext = unknown>(config: CrudRouterConfig<T
|
|
|
197
358
|
};
|
|
198
359
|
};
|
|
199
360
|
//#endregion
|
|
361
|
+
//#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
|
+
* 创建 after-only 的 middleware(最常见场景)
|
|
370
|
+
* 在操作完成后执行副作用,不修改返回值
|
|
371
|
+
*
|
|
372
|
+
* @example
|
|
373
|
+
* middleware: {
|
|
374
|
+
* create: afterMiddleware(async (ctx, result) => {
|
|
375
|
+
* await sendEmail(result);
|
|
376
|
+
* await logAudit(ctx.user, 'create', result);
|
|
377
|
+
* }),
|
|
378
|
+
* }
|
|
379
|
+
*/
|
|
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
|
+
}: {
|
|
401
|
+
ctx: TContext;
|
|
402
|
+
next: () => Promise<TResult>;
|
|
403
|
+
}) => Promise<TResult>;
|
|
404
|
+
/**
|
|
405
|
+
* 创建 before-only 的 middleware
|
|
406
|
+
* 在操作执行前修改输入数据
|
|
407
|
+
*
|
|
408
|
+
* @example
|
|
409
|
+
* middleware: {
|
|
410
|
+
* create: beforeMiddleware(async (ctx, input) => {
|
|
411
|
+
* return { ...input, slug: slugify(input.title), createdBy: ctx.user.id };
|
|
412
|
+
* }),
|
|
413
|
+
* }
|
|
414
|
+
*/
|
|
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>;
|
|
420
|
+
/**
|
|
421
|
+
* 组合多个 middleware 为一个
|
|
422
|
+
*
|
|
423
|
+
* @example
|
|
424
|
+
* middleware: {
|
|
425
|
+
* create: composeMiddleware(
|
|
426
|
+
* beforeMiddleware((ctx, input) => ({ ...input, slug: slugify(input.title) })),
|
|
427
|
+
* afterMiddleware((ctx, result) => sendEmail(result)),
|
|
428
|
+
* ),
|
|
429
|
+
* }
|
|
430
|
+
*/
|
|
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>;
|
|
488
|
+
//#endregion
|
|
200
489
|
//#region src/routers/index.d.ts
|
|
201
490
|
declare const appRouter: _trpc_server5.TRPCBuiltRouter<{
|
|
202
491
|
ctx: Context;
|
|
@@ -206,4 +495,4 @@ declare const appRouter: _trpc_server5.TRPCBuiltRouter<{
|
|
|
206
495
|
}, _trpc_server5.TRPCDecorateCreateRouterOptions<{}>>;
|
|
207
496
|
type AppRouter = typeof appRouter;
|
|
208
497
|
//#endregion
|
|
209
|
-
export { type AnyProcedure, type AppRouter, type CrudOperation, type CrudRouterConfig, type DeclarativeConfig, type FactoryConfig, type ProceduresConfig, type SoftDeleteConfig, type SoftDeleteOption, type WriteOperation, appRouter, createCrudRouter, publicProcedure, router };
|
|
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 };
|
package/dist/index.d.ts
CHANGED
|
@@ -43,7 +43,139 @@ interface SoftDeleteConfig {
|
|
|
43
43
|
* - SoftDeleteConfig: 完整配置
|
|
44
44
|
*/
|
|
45
45
|
type SoftDeleteOption = string | boolean | SoftDeleteConfig;
|
|
46
|
-
|
|
46
|
+
/**
|
|
47
|
+
* 列表查询输入类型
|
|
48
|
+
*/
|
|
49
|
+
interface ListInput {
|
|
50
|
+
page: number;
|
|
51
|
+
perPage: number;
|
|
52
|
+
sort?: Array<{
|
|
53
|
+
id: string;
|
|
54
|
+
desc: boolean;
|
|
55
|
+
}>;
|
|
56
|
+
filters?: Array<{
|
|
57
|
+
id: string;
|
|
58
|
+
value: string | string[];
|
|
59
|
+
variant: string;
|
|
60
|
+
operator: string;
|
|
61
|
+
filterId: string;
|
|
62
|
+
}>;
|
|
63
|
+
joinOperator: "and" | "or";
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* 列表查询结果类型
|
|
67
|
+
*/
|
|
68
|
+
interface ListResult<TSelect> {
|
|
69
|
+
data: TSelect[];
|
|
70
|
+
total: number;
|
|
71
|
+
page: number;
|
|
72
|
+
perPage: number;
|
|
73
|
+
pageCount: number;
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* 操作包装器 - 类似 tRPC middleware 的 next 模式
|
|
77
|
+
*
|
|
78
|
+
* 功能:
|
|
79
|
+
* - 修改输入数据(传参给 next)
|
|
80
|
+
* - 修改返回值(处理 next 的结果)
|
|
81
|
+
* - 执行副作用(日志、通知、审计)
|
|
82
|
+
* - 条件拦截(不调用 next 直接返回/抛错)
|
|
83
|
+
* - 包装事务(try/catch 包裹 next)
|
|
84
|
+
*
|
|
85
|
+
* @example
|
|
86
|
+
* // 修改输入 + 执行副作用
|
|
87
|
+
* middleware: {
|
|
88
|
+
* create: async ({ ctx, input, next }) => {
|
|
89
|
+
* const data = { ...input, slug: slugify(input.title) };
|
|
90
|
+
* const result = await next(data);
|
|
91
|
+
* await sendNotification(result);
|
|
92
|
+
* return result;
|
|
93
|
+
* },
|
|
94
|
+
* }
|
|
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
|
+
*/
|
|
106
|
+
interface CrudMiddleware<TContext = unknown> {
|
|
107
|
+
/**
|
|
108
|
+
* 列表查询包装器
|
|
109
|
+
*/
|
|
110
|
+
list?: (params: {
|
|
111
|
+
ctx: TContext;
|
|
112
|
+
input: ListInput;
|
|
113
|
+
next: (input?: ListInput) => Promise<ListResult<unknown>>;
|
|
114
|
+
}) => Promise<ListResult<unknown>>;
|
|
115
|
+
/**
|
|
116
|
+
* 单条查询包装器
|
|
117
|
+
*/
|
|
118
|
+
get?: (params: {
|
|
119
|
+
ctx: TContext;
|
|
120
|
+
id: string;
|
|
121
|
+
next: () => Promise<unknown | null>;
|
|
122
|
+
}) => Promise<unknown | null>;
|
|
123
|
+
/**
|
|
124
|
+
* 创建包装器
|
|
125
|
+
*/
|
|
126
|
+
create?: (params: {
|
|
127
|
+
ctx: TContext;
|
|
128
|
+
input: unknown;
|
|
129
|
+
next: (input?: unknown) => Promise<unknown>;
|
|
130
|
+
}) => Promise<unknown>;
|
|
131
|
+
/**
|
|
132
|
+
* 更新包装器
|
|
133
|
+
* existing: 更新前的记录
|
|
134
|
+
*/
|
|
135
|
+
update?: (params: {
|
|
136
|
+
ctx: TContext;
|
|
137
|
+
id: string;
|
|
138
|
+
data: unknown;
|
|
139
|
+
existing: unknown;
|
|
140
|
+
next: (data?: unknown) => Promise<unknown>;
|
|
141
|
+
}) => Promise<unknown>;
|
|
142
|
+
/**
|
|
143
|
+
* 删除包装器
|
|
144
|
+
* existing: 删除前的记录
|
|
145
|
+
*/
|
|
146
|
+
delete?: (params: {
|
|
147
|
+
ctx: TContext;
|
|
148
|
+
id: string;
|
|
149
|
+
existing: unknown;
|
|
150
|
+
next: () => Promise<unknown>;
|
|
151
|
+
}) => Promise<unknown>;
|
|
152
|
+
/**
|
|
153
|
+
* 批量删除包装器
|
|
154
|
+
*/
|
|
155
|
+
deleteMany?: (params: {
|
|
156
|
+
ctx: TContext;
|
|
157
|
+
ids: string[];
|
|
158
|
+
next: () => Promise<{
|
|
159
|
+
deleted: number;
|
|
160
|
+
}>;
|
|
161
|
+
}) => Promise<{
|
|
162
|
+
deleted: number;
|
|
163
|
+
}>;
|
|
164
|
+
/**
|
|
165
|
+
* 批量更新包装器
|
|
166
|
+
*/
|
|
167
|
+
updateMany?: (params: {
|
|
168
|
+
ctx: TContext;
|
|
169
|
+
ids: string[];
|
|
170
|
+
data: unknown;
|
|
171
|
+
next: (data?: unknown) => Promise<{
|
|
172
|
+
updated: number;
|
|
173
|
+
}>;
|
|
174
|
+
}) => Promise<{
|
|
175
|
+
updated: number;
|
|
176
|
+
}>;
|
|
177
|
+
}
|
|
178
|
+
interface CrudRouterConfigBase<TContext = unknown> {
|
|
47
179
|
/** Drizzle 表定义 */
|
|
48
180
|
table: PgTable;
|
|
49
181
|
/** 查询返回 Schema */
|
|
@@ -78,8 +210,37 @@ interface CrudRouterConfigBase {
|
|
|
78
210
|
* softDelete: { column: 'isDeleted', value: () => true }
|
|
79
211
|
*/
|
|
80
212
|
softDelete?: SoftDeleteOption;
|
|
213
|
+
/**
|
|
214
|
+
* 操作中间件
|
|
215
|
+
* 类似 tRPC middleware 的 next 模式,完全控制操作流程
|
|
216
|
+
*
|
|
217
|
+
* @example
|
|
218
|
+
* // 完整控制
|
|
219
|
+
* middleware: {
|
|
220
|
+
* create: async ({ ctx, input, next }) => {
|
|
221
|
+
* const data = { ...input, slug: slugify(input.title) };
|
|
222
|
+
* const result = await next(data);
|
|
223
|
+
* await sendNotification(result);
|
|
224
|
+
* return result;
|
|
225
|
+
* },
|
|
226
|
+
* }
|
|
227
|
+
*
|
|
228
|
+
* @example
|
|
229
|
+
* // 使用工具函数简化
|
|
230
|
+
* import { afterMiddleware, beforeMiddleware } from "@wordrhyme/auto-crud-server";
|
|
231
|
+
*
|
|
232
|
+
* middleware: {
|
|
233
|
+
* create: afterMiddleware(async (ctx, result) => {
|
|
234
|
+
* await sendEmail(result);
|
|
235
|
+
* }),
|
|
236
|
+
* update: beforeMiddleware(async (ctx, data) => {
|
|
237
|
+
* return { ...data, updatedAt: new Date() };
|
|
238
|
+
* }),
|
|
239
|
+
* }
|
|
240
|
+
*/
|
|
241
|
+
middleware?: CrudMiddleware<TContext>;
|
|
81
242
|
}
|
|
82
|
-
interface DeclarativeConfig<TContext = unknown> extends CrudRouterConfigBase {
|
|
243
|
+
interface DeclarativeConfig<TContext = unknown> extends CrudRouterConfigBase<TContext> {
|
|
83
244
|
mode?: "declarative";
|
|
84
245
|
/**
|
|
85
246
|
* 基础 procedure(认证)
|
|
@@ -119,7 +280,7 @@ interface DeclarativeConfig<TContext = unknown> extends CrudRouterConfigBase {
|
|
|
119
280
|
*/
|
|
120
281
|
authorize?: (ctx: TContext, resource: Record<string, unknown>, operation: CrudOperation) => boolean | Promise<boolean>;
|
|
121
282
|
}
|
|
122
|
-
interface FactoryConfig<TContext = unknown> extends CrudRouterConfigBase {
|
|
283
|
+
interface FactoryConfig<TContext = unknown> extends CrudRouterConfigBase<TContext> {
|
|
123
284
|
mode: "factory";
|
|
124
285
|
/**
|
|
125
286
|
* Procedure 工厂函数
|
|
@@ -138,7 +299,7 @@ interface FactoryConfig<TContext = unknown> extends CrudRouterConfigBase {
|
|
|
138
299
|
*/
|
|
139
300
|
inject?: (ctx: TContext, operation: WriteOperation) => Record<string, unknown>;
|
|
140
301
|
}
|
|
141
|
-
interface ProceduresConfig<TContext = unknown> extends CrudRouterConfigBase {
|
|
302
|
+
interface ProceduresConfig<TContext = unknown> extends CrudRouterConfigBase<TContext> {
|
|
142
303
|
mode: "procedures";
|
|
143
304
|
/**
|
|
144
305
|
* 按操作指定不同的 procedure
|
|
@@ -198,6 +359,134 @@ declare function createCrudRouter<TContext = unknown>(config: CrudRouterConfig<T
|
|
|
198
359
|
};
|
|
199
360
|
};
|
|
200
361
|
//#endregion
|
|
362
|
+
//#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
|
+
* 创建 after-only 的 middleware(最常见场景)
|
|
371
|
+
* 在操作完成后执行副作用,不修改返回值
|
|
372
|
+
*
|
|
373
|
+
* @example
|
|
374
|
+
* middleware: {
|
|
375
|
+
* create: afterMiddleware(async (ctx, result) => {
|
|
376
|
+
* await sendEmail(result);
|
|
377
|
+
* await logAudit(ctx.user, 'create', result);
|
|
378
|
+
* }),
|
|
379
|
+
* }
|
|
380
|
+
*/
|
|
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
|
+
}: {
|
|
402
|
+
ctx: TContext;
|
|
403
|
+
next: () => Promise<TResult>;
|
|
404
|
+
}) => Promise<TResult>;
|
|
405
|
+
/**
|
|
406
|
+
* 创建 before-only 的 middleware
|
|
407
|
+
* 在操作执行前修改输入数据
|
|
408
|
+
*
|
|
409
|
+
* @example
|
|
410
|
+
* middleware: {
|
|
411
|
+
* create: beforeMiddleware(async (ctx, input) => {
|
|
412
|
+
* return { ...input, slug: slugify(input.title), createdBy: ctx.user.id };
|
|
413
|
+
* }),
|
|
414
|
+
* }
|
|
415
|
+
*/
|
|
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>;
|
|
421
|
+
/**
|
|
422
|
+
* 组合多个 middleware 为一个
|
|
423
|
+
*
|
|
424
|
+
* @example
|
|
425
|
+
* middleware: {
|
|
426
|
+
* create: composeMiddleware(
|
|
427
|
+
* beforeMiddleware((ctx, input) => ({ ...input, slug: slugify(input.title) })),
|
|
428
|
+
* afterMiddleware((ctx, result) => sendEmail(result)),
|
|
429
|
+
* ),
|
|
430
|
+
* }
|
|
431
|
+
*/
|
|
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>;
|
|
489
|
+
//#endregion
|
|
201
490
|
//#region src/routers/index.d.ts
|
|
202
491
|
declare const appRouter: _trpc_server5.TRPCBuiltRouter<{
|
|
203
492
|
ctx: Context;
|
|
@@ -207,4 +496,4 @@ declare const appRouter: _trpc_server5.TRPCBuiltRouter<{
|
|
|
207
496
|
}, _trpc_server5.TRPCDecorateCreateRouterOptions<{}>>;
|
|
208
497
|
type AppRouter = typeof appRouter;
|
|
209
498
|
//#endregion
|
|
210
|
-
export { type AnyProcedure, type AppRouter, type CrudOperation, type CrudRouterConfig, type DeclarativeConfig, type FactoryConfig, type ProceduresConfig, type SoftDeleteConfig, type SoftDeleteOption, type WriteOperation, appRouter, createCrudRouter, publicProcedure, router };
|
|
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 };
|