@wordrhyme/auto-crud-server 0.6.2 → 0.8.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/dist/index.cjs +43 -28
- package/dist/index.d.cts +126 -88
- package/dist/index.d.ts +126 -88
- package/dist/index.js +43 -28
- package/package.json +3 -3
package/dist/index.cjs
CHANGED
|
@@ -417,38 +417,53 @@ function resolveSoftDelete(option) {
|
|
|
417
417
|
getValue: config.value ?? (() => /* @__PURE__ */ new Date())
|
|
418
418
|
};
|
|
419
419
|
}
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
420
|
+
/**
|
|
421
|
+
* 判断 procedure 配置的类型
|
|
422
|
+
*/
|
|
423
|
+
function isProcedureMap(config) {
|
|
424
|
+
if (!config) return false;
|
|
425
|
+
if (typeof config === "function") return false;
|
|
426
|
+
if (typeof config === "object" && config !== null) {
|
|
427
|
+
const keys = Object.keys(config);
|
|
428
|
+
const validKeys = [
|
|
429
|
+
"list",
|
|
430
|
+
"get",
|
|
431
|
+
"create",
|
|
432
|
+
"update",
|
|
433
|
+
"delete",
|
|
434
|
+
"deleteMany",
|
|
435
|
+
"updateMany",
|
|
436
|
+
"upsert",
|
|
437
|
+
"default"
|
|
438
|
+
];
|
|
439
|
+
return keys.some((k) => validKeys.includes(k));
|
|
431
440
|
}
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
441
|
+
return false;
|
|
442
|
+
}
|
|
443
|
+
function isProcedureFactory(config) {
|
|
444
|
+
return typeof config === "function";
|
|
445
|
+
}
|
|
446
|
+
/**
|
|
447
|
+
* 解析 procedure 配置为工厂函数
|
|
448
|
+
*/
|
|
449
|
+
function resolveProcedureFactory(config) {
|
|
450
|
+
if (!config) return () => publicProcedure;
|
|
451
|
+
if (isProcedureFactory(config)) return config;
|
|
452
|
+
if (isProcedureMap(config)) {
|
|
453
|
+
const map = config;
|
|
454
|
+
const defaultProc = map.default ?? publicProcedure;
|
|
455
|
+
return (op) => map[op] ?? defaultProc;
|
|
442
456
|
}
|
|
443
|
-
|
|
444
|
-
|
|
457
|
+
return () => config;
|
|
458
|
+
}
|
|
459
|
+
function resolveConfig(config) {
|
|
445
460
|
return {
|
|
446
|
-
procedureFactory: ()
|
|
447
|
-
guard:
|
|
448
|
-
getScope: (ctx, tbl, op) =>
|
|
449
|
-
getInject: (ctx, op) =>
|
|
461
|
+
procedureFactory: resolveProcedureFactory(config.procedure),
|
|
462
|
+
guard: config.guard,
|
|
463
|
+
getScope: (ctx, tbl, op) => config.scope?.(ctx, tbl, op),
|
|
464
|
+
getInject: (ctx, op) => config.inject?.(ctx, op) ?? {},
|
|
450
465
|
checkAuthorize: async (ctx, resource, op) => {
|
|
451
|
-
if (
|
|
466
|
+
if (config.authorize) return config.authorize(ctx, resource, op);
|
|
452
467
|
return true;
|
|
453
468
|
}
|
|
454
469
|
};
|
package/dist/index.d.cts
CHANGED
|
@@ -191,15 +191,137 @@ interface CrudMiddleware<TContext = unknown, TSelect = unknown, TInsert = unknow
|
|
|
191
191
|
isNew: boolean;
|
|
192
192
|
}>;
|
|
193
193
|
}
|
|
194
|
-
|
|
194
|
+
/**
|
|
195
|
+
* Procedure 映射对象
|
|
196
|
+
* 按操作指定不同的 procedure,支持 default 回退
|
|
197
|
+
*/
|
|
198
|
+
interface ProcedureMap {
|
|
199
|
+
list?: AnyProcedure;
|
|
200
|
+
get?: AnyProcedure;
|
|
201
|
+
create?: AnyProcedure;
|
|
202
|
+
update?: AnyProcedure;
|
|
203
|
+
delete?: AnyProcedure;
|
|
204
|
+
deleteMany?: AnyProcedure;
|
|
205
|
+
updateMany?: AnyProcedure;
|
|
206
|
+
upsert?: AnyProcedure;
|
|
207
|
+
/** 未指定操作时的默认 procedure */
|
|
208
|
+
default?: AnyProcedure;
|
|
209
|
+
}
|
|
210
|
+
/**
|
|
211
|
+
* Procedure 工厂函数
|
|
212
|
+
* 根据操作类型动态返回 procedure
|
|
213
|
+
*/
|
|
214
|
+
type ProcedureFactory = (operation: CrudOperation) => AnyProcedure;
|
|
215
|
+
/**
|
|
216
|
+
* 统一的 Procedure 配置
|
|
217
|
+
* 支持三种形式:
|
|
218
|
+
* 1. 单一 procedure - 所有操作共用
|
|
219
|
+
* 2. 对象映射 - 按操作指定
|
|
220
|
+
* 3. 工厂函数 - 动态生成
|
|
221
|
+
*
|
|
222
|
+
* @example
|
|
223
|
+
* // 形式 1:单一 procedure
|
|
224
|
+
* procedure: protectedProcedure
|
|
225
|
+
*
|
|
226
|
+
* // 形式 2:对象映射(推荐常用场景)
|
|
227
|
+
* procedure: {
|
|
228
|
+
* list: publicProcedure,
|
|
229
|
+
* get: publicProcedure,
|
|
230
|
+
* create: editorProcedure,
|
|
231
|
+
* delete: adminProcedure,
|
|
232
|
+
* default: protectedProcedure,
|
|
233
|
+
* }
|
|
234
|
+
*
|
|
235
|
+
* // 形式 3:工厂函数(适合复杂逻辑)
|
|
236
|
+
* procedure: (op) => {
|
|
237
|
+
* if (['list', 'get'].includes(op)) return publicProcedure;
|
|
238
|
+
* return protectedProcedure.meta({ permission: { action: op, subject: 'Task' } });
|
|
239
|
+
* }
|
|
240
|
+
*/
|
|
241
|
+
type ProcedureConfig = AnyProcedure | ProcedureMap | ProcedureFactory;
|
|
242
|
+
/**
|
|
243
|
+
* CRUD Router 配置
|
|
244
|
+
*
|
|
245
|
+
* v2.0 统一 API 设计:
|
|
246
|
+
* - 所有配置项可自由组合,无互斥模式
|
|
247
|
+
* - procedure 支持多种形式(单一/映射/工厂)
|
|
248
|
+
* - guard/scope/authorize 可与任意 procedure 形式组合
|
|
249
|
+
*
|
|
250
|
+
* @example
|
|
251
|
+
* // 简单场景:公开读,保护写
|
|
252
|
+
* createCrudRouter({
|
|
253
|
+
* table: posts,
|
|
254
|
+
* insertSchema,
|
|
255
|
+
* updateSchema,
|
|
256
|
+
* procedure: {
|
|
257
|
+
* list: publicProcedure,
|
|
258
|
+
* get: publicProcedure,
|
|
259
|
+
* default: protectedProcedure,
|
|
260
|
+
* },
|
|
261
|
+
* })
|
|
262
|
+
*
|
|
263
|
+
* // 复杂场景:自定义 procedure + 行级安全
|
|
264
|
+
* createCrudRouter({
|
|
265
|
+
* table: posts,
|
|
266
|
+
* insertSchema,
|
|
267
|
+
* updateSchema,
|
|
268
|
+
* procedure: adminProcedure,
|
|
269
|
+
* scope: (ctx, table) => eq(table.tenantId, ctx.user.tenantId),
|
|
270
|
+
* authorize: (ctx, resource) => resource.ownerId === ctx.user.id,
|
|
271
|
+
* })
|
|
272
|
+
*/
|
|
273
|
+
interface CrudRouterConfig<TContext = unknown, TSelect = unknown, TInsert = unknown, TUpdate = unknown> {
|
|
195
274
|
/** Drizzle 表定义 */
|
|
196
275
|
table: PgTable;
|
|
197
|
-
/** 查询返回 Schema */
|
|
198
|
-
selectSchema?: z.ZodType<TSelect>;
|
|
199
276
|
/** 创建输入 Schema */
|
|
200
277
|
insertSchema: z.ZodType<TInsert>;
|
|
201
278
|
/** 更新输入 Schema */
|
|
202
279
|
updateSchema: z.ZodType<TUpdate>;
|
|
280
|
+
/**
|
|
281
|
+
* Procedure 配置(统一字段)
|
|
282
|
+
*
|
|
283
|
+
* 支持三种形式:
|
|
284
|
+
* 1. 单一 procedure - 所有操作共用
|
|
285
|
+
* 2. 对象映射 - 按操作指定(推荐)
|
|
286
|
+
* 3. 工厂函数 - 动态生成
|
|
287
|
+
*
|
|
288
|
+
* 默认: publicProcedure
|
|
289
|
+
*/
|
|
290
|
+
procedure?: ProcedureConfig;
|
|
291
|
+
/**
|
|
292
|
+
* 操作级守卫(RBAC)
|
|
293
|
+
* 在 scope 之前执行,快速拒绝无权限请求
|
|
294
|
+
*
|
|
295
|
+
* @example
|
|
296
|
+
* guard: (ctx, op) => op === 'delete' ? ctx.user.role === 'admin' : true
|
|
297
|
+
*/
|
|
298
|
+
guard?: (ctx: TContext, operation: CrudOperation) => boolean | Promise<boolean>;
|
|
299
|
+
/**
|
|
300
|
+
* 行级过滤(RLS)
|
|
301
|
+
* 返回 SQL 条件,自动注入到 WHERE 子句
|
|
302
|
+
*
|
|
303
|
+
* @example
|
|
304
|
+
* scope: (ctx, table) => eq(table.tenantId, ctx.user.tenantId)
|
|
305
|
+
*/
|
|
306
|
+
scope?: (ctx: TContext, table: PgTable, operation: CrudOperation) => SQL | undefined;
|
|
307
|
+
/**
|
|
308
|
+
* 强制注入字段(写入时)
|
|
309
|
+
* 返回的字段会强制覆盖用户输入(防止伪造)
|
|
310
|
+
*
|
|
311
|
+
* @example
|
|
312
|
+
* inject: (ctx) => ({ tenantId: ctx.user.tenantId, createdBy: ctx.user.id })
|
|
313
|
+
*/
|
|
314
|
+
inject?: (ctx: TContext, operation: WriteOperation) => Record<string, unknown>;
|
|
315
|
+
/**
|
|
316
|
+
* 资源级检查(ABAC)
|
|
317
|
+
* 在 get/update/delete 时,预取资源后调用
|
|
318
|
+
*
|
|
319
|
+
* @example
|
|
320
|
+
* authorize: (ctx, resource, op) => resource.ownerId === ctx.user.id
|
|
321
|
+
*/
|
|
322
|
+
authorize?: (ctx: TContext, resource: TSelect, operation: CrudOperation) => boolean | Promise<boolean>;
|
|
323
|
+
/** 查询返回 Schema */
|
|
324
|
+
selectSchema?: z.ZodType<TSelect>;
|
|
203
325
|
/** ID 字段名,默认 "id" */
|
|
204
326
|
idField?: string;
|
|
205
327
|
/** 可过滤的列白名单 */
|
|
@@ -256,90 +378,6 @@ interface CrudRouterConfigBase<TContext = unknown, TSelect = unknown, TInsert =
|
|
|
256
378
|
*/
|
|
257
379
|
middleware?: CrudMiddleware<TContext, TSelect, TInsert, TUpdate>;
|
|
258
380
|
}
|
|
259
|
-
interface DeclarativeConfig<TContext = unknown, TSelect = unknown, TInsert = unknown, TUpdate = unknown> extends CrudRouterConfigBase<TContext, TSelect, TInsert, TUpdate> {
|
|
260
|
-
mode?: "declarative";
|
|
261
|
-
/**
|
|
262
|
-
* 基础 procedure(认证)
|
|
263
|
-
* 所有操作共用,除非被 guard 拦截
|
|
264
|
-
*/
|
|
265
|
-
procedure?: AnyProcedure;
|
|
266
|
-
/**
|
|
267
|
-
* 操作级守卫(RBAC)
|
|
268
|
-
* 在 scope 之前执行,快速拒绝无权限请求
|
|
269
|
-
*
|
|
270
|
-
* @example
|
|
271
|
-
* guard: (ctx, op) => op === 'delete' ? ctx.user.role === 'admin' : true
|
|
272
|
-
*/
|
|
273
|
-
guard?: (ctx: TContext, operation: CrudOperation) => boolean | Promise<boolean>;
|
|
274
|
-
/**
|
|
275
|
-
* 行级过滤(RLS)
|
|
276
|
-
* 返回 SQL 条件,自动注入到 WHERE 子句
|
|
277
|
-
*
|
|
278
|
-
* @example
|
|
279
|
-
* scope: (ctx, table) => eq(table.tenantId, ctx.user.tenantId)
|
|
280
|
-
*/
|
|
281
|
-
scope?: (ctx: TContext, table: PgTable, operation: CrudOperation) => SQL | undefined;
|
|
282
|
-
/**
|
|
283
|
-
* 强制注入字段(写入时)
|
|
284
|
-
* 返回的字段会强制覆盖用户输入(防止伪造)
|
|
285
|
-
*
|
|
286
|
-
* @example
|
|
287
|
-
* inject: (ctx) => ({ tenantId: ctx.user.tenantId, createdBy: ctx.user.id })
|
|
288
|
-
*/
|
|
289
|
-
inject?: (ctx: TContext, operation: WriteOperation) => Record<string, unknown>;
|
|
290
|
-
/**
|
|
291
|
-
* 资源级检查(ABAC)
|
|
292
|
-
* 在 get/update/delete 时,预取资源后调用
|
|
293
|
-
*
|
|
294
|
-
* @example
|
|
295
|
-
* authorize: (ctx, resource, op) => resource.ownerId === ctx.user.id
|
|
296
|
-
*/
|
|
297
|
-
authorize?: (ctx: TContext, resource: TSelect, operation: CrudOperation) => boolean | Promise<boolean>;
|
|
298
|
-
}
|
|
299
|
-
interface FactoryConfig<TContext = unknown, TSelect = unknown, TInsert = unknown, TUpdate = unknown> extends CrudRouterConfigBase<TContext, TSelect, TInsert, TUpdate> {
|
|
300
|
-
mode: "factory";
|
|
301
|
-
/**
|
|
302
|
-
* Procedure 工厂函数
|
|
303
|
-
* 根据操作类型返回对应的 procedure
|
|
304
|
-
*
|
|
305
|
-
* @example
|
|
306
|
-
* procedureFactory: (op) => {
|
|
307
|
-
* if (['list', 'get'].includes(op)) return publicProcedure;
|
|
308
|
-
* return protectedProcedure.meta({ permission: { action: op, subject: 'Task' } });
|
|
309
|
-
* }
|
|
310
|
-
*/
|
|
311
|
-
procedureFactory: (operation: CrudOperation) => AnyProcedure;
|
|
312
|
-
/**
|
|
313
|
-
* 强制注入字段(可选)
|
|
314
|
-
* 如果 DB 层已处理则不需要
|
|
315
|
-
*/
|
|
316
|
-
inject?: (ctx: TContext, operation: WriteOperation) => Record<string, unknown>;
|
|
317
|
-
}
|
|
318
|
-
interface ProceduresConfig<TContext = unknown, TSelect = unknown, TInsert = unknown, TUpdate = unknown> extends CrudRouterConfigBase<TContext, TSelect, TInsert, TUpdate> {
|
|
319
|
-
mode: "procedures";
|
|
320
|
-
/**
|
|
321
|
-
* 按操作指定不同的 procedure
|
|
322
|
-
*
|
|
323
|
-
* @example
|
|
324
|
-
* procedures: {
|
|
325
|
-
* list: publicProcedure,
|
|
326
|
-
* get: publicProcedure,
|
|
327
|
-
* create: editorProcedure,
|
|
328
|
-
* update: editorProcedure,
|
|
329
|
-
* delete: adminProcedure,
|
|
330
|
-
* }
|
|
331
|
-
*/
|
|
332
|
-
procedures: Partial<Record<CrudOperation, AnyProcedure>>;
|
|
333
|
-
/**
|
|
334
|
-
* 默认 procedure(未指定操作时使用)
|
|
335
|
-
*/
|
|
336
|
-
defaultProcedure?: AnyProcedure;
|
|
337
|
-
/**
|
|
338
|
-
* 强制注入字段(可选)
|
|
339
|
-
*/
|
|
340
|
-
inject?: (ctx: TContext, operation: WriteOperation) => Record<string, unknown>;
|
|
341
|
-
}
|
|
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
381
|
interface CrudProcedures<TSelect, TInsert, TUpdate> {
|
|
344
382
|
list: AnyProcedure;
|
|
345
383
|
get: AnyProcedure;
|
|
@@ -430,4 +468,4 @@ declare const appRouter: _trpc_server2.TRPCBuiltRouter<{
|
|
|
430
468
|
}, _trpc_server2.TRPCDecorateCreateRouterOptions<{}>>;
|
|
431
469
|
type AppRouter = typeof appRouter;
|
|
432
470
|
//#endregion
|
|
433
|
-
export { type AnyProcedure, type AppRouter, type CreateMiddlewareParams, type CrudMiddleware, type CrudOperation, type CrudProcedures, type CrudRouterConfig, type
|
|
471
|
+
export { type AnyProcedure, type AppRouter, type CreateMiddlewareParams, type CrudMiddleware, type CrudOperation, type CrudProcedures, type CrudRouterConfig, type DeleteManyMiddlewareParams, type DeleteMiddlewareParams, type GetMiddlewareParams, type ListInput, type ListMiddlewareParams, type ListResult, type ProcedureConfig, type ProcedureFactory, type ProcedureMap, 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
|
@@ -192,15 +192,137 @@ interface CrudMiddleware<TContext = unknown, TSelect = unknown, TInsert = unknow
|
|
|
192
192
|
isNew: boolean;
|
|
193
193
|
}>;
|
|
194
194
|
}
|
|
195
|
-
|
|
195
|
+
/**
|
|
196
|
+
* Procedure 映射对象
|
|
197
|
+
* 按操作指定不同的 procedure,支持 default 回退
|
|
198
|
+
*/
|
|
199
|
+
interface ProcedureMap {
|
|
200
|
+
list?: AnyProcedure;
|
|
201
|
+
get?: AnyProcedure;
|
|
202
|
+
create?: AnyProcedure;
|
|
203
|
+
update?: AnyProcedure;
|
|
204
|
+
delete?: AnyProcedure;
|
|
205
|
+
deleteMany?: AnyProcedure;
|
|
206
|
+
updateMany?: AnyProcedure;
|
|
207
|
+
upsert?: AnyProcedure;
|
|
208
|
+
/** 未指定操作时的默认 procedure */
|
|
209
|
+
default?: AnyProcedure;
|
|
210
|
+
}
|
|
211
|
+
/**
|
|
212
|
+
* Procedure 工厂函数
|
|
213
|
+
* 根据操作类型动态返回 procedure
|
|
214
|
+
*/
|
|
215
|
+
type ProcedureFactory = (operation: CrudOperation) => AnyProcedure;
|
|
216
|
+
/**
|
|
217
|
+
* 统一的 Procedure 配置
|
|
218
|
+
* 支持三种形式:
|
|
219
|
+
* 1. 单一 procedure - 所有操作共用
|
|
220
|
+
* 2. 对象映射 - 按操作指定
|
|
221
|
+
* 3. 工厂函数 - 动态生成
|
|
222
|
+
*
|
|
223
|
+
* @example
|
|
224
|
+
* // 形式 1:单一 procedure
|
|
225
|
+
* procedure: protectedProcedure
|
|
226
|
+
*
|
|
227
|
+
* // 形式 2:对象映射(推荐常用场景)
|
|
228
|
+
* procedure: {
|
|
229
|
+
* list: publicProcedure,
|
|
230
|
+
* get: publicProcedure,
|
|
231
|
+
* create: editorProcedure,
|
|
232
|
+
* delete: adminProcedure,
|
|
233
|
+
* default: protectedProcedure,
|
|
234
|
+
* }
|
|
235
|
+
*
|
|
236
|
+
* // 形式 3:工厂函数(适合复杂逻辑)
|
|
237
|
+
* procedure: (op) => {
|
|
238
|
+
* if (['list', 'get'].includes(op)) return publicProcedure;
|
|
239
|
+
* return protectedProcedure.meta({ permission: { action: op, subject: 'Task' } });
|
|
240
|
+
* }
|
|
241
|
+
*/
|
|
242
|
+
type ProcedureConfig = AnyProcedure | ProcedureMap | ProcedureFactory;
|
|
243
|
+
/**
|
|
244
|
+
* CRUD Router 配置
|
|
245
|
+
*
|
|
246
|
+
* v2.0 统一 API 设计:
|
|
247
|
+
* - 所有配置项可自由组合,无互斥模式
|
|
248
|
+
* - procedure 支持多种形式(单一/映射/工厂)
|
|
249
|
+
* - guard/scope/authorize 可与任意 procedure 形式组合
|
|
250
|
+
*
|
|
251
|
+
* @example
|
|
252
|
+
* // 简单场景:公开读,保护写
|
|
253
|
+
* createCrudRouter({
|
|
254
|
+
* table: posts,
|
|
255
|
+
* insertSchema,
|
|
256
|
+
* updateSchema,
|
|
257
|
+
* procedure: {
|
|
258
|
+
* list: publicProcedure,
|
|
259
|
+
* get: publicProcedure,
|
|
260
|
+
* default: protectedProcedure,
|
|
261
|
+
* },
|
|
262
|
+
* })
|
|
263
|
+
*
|
|
264
|
+
* // 复杂场景:自定义 procedure + 行级安全
|
|
265
|
+
* createCrudRouter({
|
|
266
|
+
* table: posts,
|
|
267
|
+
* insertSchema,
|
|
268
|
+
* updateSchema,
|
|
269
|
+
* procedure: adminProcedure,
|
|
270
|
+
* scope: (ctx, table) => eq(table.tenantId, ctx.user.tenantId),
|
|
271
|
+
* authorize: (ctx, resource) => resource.ownerId === ctx.user.id,
|
|
272
|
+
* })
|
|
273
|
+
*/
|
|
274
|
+
interface CrudRouterConfig<TContext = unknown, TSelect = unknown, TInsert = unknown, TUpdate = unknown> {
|
|
196
275
|
/** Drizzle 表定义 */
|
|
197
276
|
table: PgTable;
|
|
198
|
-
/** 查询返回 Schema */
|
|
199
|
-
selectSchema?: z.ZodType<TSelect>;
|
|
200
277
|
/** 创建输入 Schema */
|
|
201
278
|
insertSchema: z.ZodType<TInsert>;
|
|
202
279
|
/** 更新输入 Schema */
|
|
203
280
|
updateSchema: z.ZodType<TUpdate>;
|
|
281
|
+
/**
|
|
282
|
+
* Procedure 配置(统一字段)
|
|
283
|
+
*
|
|
284
|
+
* 支持三种形式:
|
|
285
|
+
* 1. 单一 procedure - 所有操作共用
|
|
286
|
+
* 2. 对象映射 - 按操作指定(推荐)
|
|
287
|
+
* 3. 工厂函数 - 动态生成
|
|
288
|
+
*
|
|
289
|
+
* 默认: publicProcedure
|
|
290
|
+
*/
|
|
291
|
+
procedure?: ProcedureConfig;
|
|
292
|
+
/**
|
|
293
|
+
* 操作级守卫(RBAC)
|
|
294
|
+
* 在 scope 之前执行,快速拒绝无权限请求
|
|
295
|
+
*
|
|
296
|
+
* @example
|
|
297
|
+
* guard: (ctx, op) => op === 'delete' ? ctx.user.role === 'admin' : true
|
|
298
|
+
*/
|
|
299
|
+
guard?: (ctx: TContext, operation: CrudOperation) => boolean | Promise<boolean>;
|
|
300
|
+
/**
|
|
301
|
+
* 行级过滤(RLS)
|
|
302
|
+
* 返回 SQL 条件,自动注入到 WHERE 子句
|
|
303
|
+
*
|
|
304
|
+
* @example
|
|
305
|
+
* scope: (ctx, table) => eq(table.tenantId, ctx.user.tenantId)
|
|
306
|
+
*/
|
|
307
|
+
scope?: (ctx: TContext, table: PgTable, operation: CrudOperation) => SQL | undefined;
|
|
308
|
+
/**
|
|
309
|
+
* 强制注入字段(写入时)
|
|
310
|
+
* 返回的字段会强制覆盖用户输入(防止伪造)
|
|
311
|
+
*
|
|
312
|
+
* @example
|
|
313
|
+
* inject: (ctx) => ({ tenantId: ctx.user.tenantId, createdBy: ctx.user.id })
|
|
314
|
+
*/
|
|
315
|
+
inject?: (ctx: TContext, operation: WriteOperation) => Record<string, unknown>;
|
|
316
|
+
/**
|
|
317
|
+
* 资源级检查(ABAC)
|
|
318
|
+
* 在 get/update/delete 时,预取资源后调用
|
|
319
|
+
*
|
|
320
|
+
* @example
|
|
321
|
+
* authorize: (ctx, resource, op) => resource.ownerId === ctx.user.id
|
|
322
|
+
*/
|
|
323
|
+
authorize?: (ctx: TContext, resource: TSelect, operation: CrudOperation) => boolean | Promise<boolean>;
|
|
324
|
+
/** 查询返回 Schema */
|
|
325
|
+
selectSchema?: z.ZodType<TSelect>;
|
|
204
326
|
/** ID 字段名,默认 "id" */
|
|
205
327
|
idField?: string;
|
|
206
328
|
/** 可过滤的列白名单 */
|
|
@@ -257,90 +379,6 @@ interface CrudRouterConfigBase<TContext = unknown, TSelect = unknown, TInsert =
|
|
|
257
379
|
*/
|
|
258
380
|
middleware?: CrudMiddleware<TContext, TSelect, TInsert, TUpdate>;
|
|
259
381
|
}
|
|
260
|
-
interface DeclarativeConfig<TContext = unknown, TSelect = unknown, TInsert = unknown, TUpdate = unknown> extends CrudRouterConfigBase<TContext, TSelect, TInsert, TUpdate> {
|
|
261
|
-
mode?: "declarative";
|
|
262
|
-
/**
|
|
263
|
-
* 基础 procedure(认证)
|
|
264
|
-
* 所有操作共用,除非被 guard 拦截
|
|
265
|
-
*/
|
|
266
|
-
procedure?: AnyProcedure;
|
|
267
|
-
/**
|
|
268
|
-
* 操作级守卫(RBAC)
|
|
269
|
-
* 在 scope 之前执行,快速拒绝无权限请求
|
|
270
|
-
*
|
|
271
|
-
* @example
|
|
272
|
-
* guard: (ctx, op) => op === 'delete' ? ctx.user.role === 'admin' : true
|
|
273
|
-
*/
|
|
274
|
-
guard?: (ctx: TContext, operation: CrudOperation) => boolean | Promise<boolean>;
|
|
275
|
-
/**
|
|
276
|
-
* 行级过滤(RLS)
|
|
277
|
-
* 返回 SQL 条件,自动注入到 WHERE 子句
|
|
278
|
-
*
|
|
279
|
-
* @example
|
|
280
|
-
* scope: (ctx, table) => eq(table.tenantId, ctx.user.tenantId)
|
|
281
|
-
*/
|
|
282
|
-
scope?: (ctx: TContext, table: PgTable, operation: CrudOperation) => SQL | undefined;
|
|
283
|
-
/**
|
|
284
|
-
* 强制注入字段(写入时)
|
|
285
|
-
* 返回的字段会强制覆盖用户输入(防止伪造)
|
|
286
|
-
*
|
|
287
|
-
* @example
|
|
288
|
-
* inject: (ctx) => ({ tenantId: ctx.user.tenantId, createdBy: ctx.user.id })
|
|
289
|
-
*/
|
|
290
|
-
inject?: (ctx: TContext, operation: WriteOperation) => Record<string, unknown>;
|
|
291
|
-
/**
|
|
292
|
-
* 资源级检查(ABAC)
|
|
293
|
-
* 在 get/update/delete 时,预取资源后调用
|
|
294
|
-
*
|
|
295
|
-
* @example
|
|
296
|
-
* authorize: (ctx, resource, op) => resource.ownerId === ctx.user.id
|
|
297
|
-
*/
|
|
298
|
-
authorize?: (ctx: TContext, resource: TSelect, operation: CrudOperation) => boolean | Promise<boolean>;
|
|
299
|
-
}
|
|
300
|
-
interface FactoryConfig<TContext = unknown, TSelect = unknown, TInsert = unknown, TUpdate = unknown> extends CrudRouterConfigBase<TContext, TSelect, TInsert, TUpdate> {
|
|
301
|
-
mode: "factory";
|
|
302
|
-
/**
|
|
303
|
-
* Procedure 工厂函数
|
|
304
|
-
* 根据操作类型返回对应的 procedure
|
|
305
|
-
*
|
|
306
|
-
* @example
|
|
307
|
-
* procedureFactory: (op) => {
|
|
308
|
-
* if (['list', 'get'].includes(op)) return publicProcedure;
|
|
309
|
-
* return protectedProcedure.meta({ permission: { action: op, subject: 'Task' } });
|
|
310
|
-
* }
|
|
311
|
-
*/
|
|
312
|
-
procedureFactory: (operation: CrudOperation) => AnyProcedure;
|
|
313
|
-
/**
|
|
314
|
-
* 强制注入字段(可选)
|
|
315
|
-
* 如果 DB 层已处理则不需要
|
|
316
|
-
*/
|
|
317
|
-
inject?: (ctx: TContext, operation: WriteOperation) => Record<string, unknown>;
|
|
318
|
-
}
|
|
319
|
-
interface ProceduresConfig<TContext = unknown, TSelect = unknown, TInsert = unknown, TUpdate = unknown> extends CrudRouterConfigBase<TContext, TSelect, TInsert, TUpdate> {
|
|
320
|
-
mode: "procedures";
|
|
321
|
-
/**
|
|
322
|
-
* 按操作指定不同的 procedure
|
|
323
|
-
*
|
|
324
|
-
* @example
|
|
325
|
-
* procedures: {
|
|
326
|
-
* list: publicProcedure,
|
|
327
|
-
* get: publicProcedure,
|
|
328
|
-
* create: editorProcedure,
|
|
329
|
-
* update: editorProcedure,
|
|
330
|
-
* delete: adminProcedure,
|
|
331
|
-
* }
|
|
332
|
-
*/
|
|
333
|
-
procedures: Partial<Record<CrudOperation, AnyProcedure>>;
|
|
334
|
-
/**
|
|
335
|
-
* 默认 procedure(未指定操作时使用)
|
|
336
|
-
*/
|
|
337
|
-
defaultProcedure?: AnyProcedure;
|
|
338
|
-
/**
|
|
339
|
-
* 强制注入字段(可选)
|
|
340
|
-
*/
|
|
341
|
-
inject?: (ctx: TContext, operation: WriteOperation) => Record<string, unknown>;
|
|
342
|
-
}
|
|
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
382
|
interface CrudProcedures<TSelect, TInsert, TUpdate> {
|
|
345
383
|
list: AnyProcedure;
|
|
346
384
|
get: AnyProcedure;
|
|
@@ -431,4 +469,4 @@ declare const appRouter: _trpc_server2.TRPCBuiltRouter<{
|
|
|
431
469
|
}, _trpc_server2.TRPCDecorateCreateRouterOptions<{}>>;
|
|
432
470
|
type AppRouter = typeof appRouter;
|
|
433
471
|
//#endregion
|
|
434
|
-
export { type AnyProcedure, type AppRouter, type CreateMiddlewareParams, type CrudMiddleware, type CrudOperation, type CrudProcedures, type CrudRouterConfig, type
|
|
472
|
+
export { type AnyProcedure, type AppRouter, type CreateMiddlewareParams, type CrudMiddleware, type CrudOperation, type CrudProcedures, type CrudRouterConfig, type DeleteManyMiddlewareParams, type DeleteMiddlewareParams, type GetMiddlewareParams, type ListInput, type ListMiddlewareParams, type ListResult, type ProcedureConfig, type ProcedureFactory, type ProcedureMap, type SoftDeleteConfig, type SoftDeleteOption, type UpdateManyMiddlewareParams, type UpdateMiddlewareParams, type UpsertMiddlewareParams, type WriteOperation, afterMiddleware, appRouter, beforeMiddleware, composeMiddleware, createCrudRouter, publicProcedure, router };
|
package/dist/index.js
CHANGED
|
@@ -389,38 +389,53 @@ function resolveSoftDelete(option) {
|
|
|
389
389
|
getValue: config.value ?? (() => /* @__PURE__ */ new Date())
|
|
390
390
|
};
|
|
391
391
|
}
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
392
|
+
/**
|
|
393
|
+
* 判断 procedure 配置的类型
|
|
394
|
+
*/
|
|
395
|
+
function isProcedureMap(config) {
|
|
396
|
+
if (!config) return false;
|
|
397
|
+
if (typeof config === "function") return false;
|
|
398
|
+
if (typeof config === "object" && config !== null) {
|
|
399
|
+
const keys = Object.keys(config);
|
|
400
|
+
const validKeys = [
|
|
401
|
+
"list",
|
|
402
|
+
"get",
|
|
403
|
+
"create",
|
|
404
|
+
"update",
|
|
405
|
+
"delete",
|
|
406
|
+
"deleteMany",
|
|
407
|
+
"updateMany",
|
|
408
|
+
"upsert",
|
|
409
|
+
"default"
|
|
410
|
+
];
|
|
411
|
+
return keys.some((k) => validKeys.includes(k));
|
|
403
412
|
}
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
413
|
+
return false;
|
|
414
|
+
}
|
|
415
|
+
function isProcedureFactory(config) {
|
|
416
|
+
return typeof config === "function";
|
|
417
|
+
}
|
|
418
|
+
/**
|
|
419
|
+
* 解析 procedure 配置为工厂函数
|
|
420
|
+
*/
|
|
421
|
+
function resolveProcedureFactory(config) {
|
|
422
|
+
if (!config) return () => publicProcedure;
|
|
423
|
+
if (isProcedureFactory(config)) return config;
|
|
424
|
+
if (isProcedureMap(config)) {
|
|
425
|
+
const map = config;
|
|
426
|
+
const defaultProc = map.default ?? publicProcedure;
|
|
427
|
+
return (op) => map[op] ?? defaultProc;
|
|
414
428
|
}
|
|
415
|
-
|
|
416
|
-
|
|
429
|
+
return () => config;
|
|
430
|
+
}
|
|
431
|
+
function resolveConfig(config) {
|
|
417
432
|
return {
|
|
418
|
-
procedureFactory: ()
|
|
419
|
-
guard:
|
|
420
|
-
getScope: (ctx, tbl, op) =>
|
|
421
|
-
getInject: (ctx, op) =>
|
|
433
|
+
procedureFactory: resolveProcedureFactory(config.procedure),
|
|
434
|
+
guard: config.guard,
|
|
435
|
+
getScope: (ctx, tbl, op) => config.scope?.(ctx, tbl, op),
|
|
436
|
+
getInject: (ctx, op) => config.inject?.(ctx, op) ?? {},
|
|
422
437
|
checkAuthorize: async (ctx, resource, op) => {
|
|
423
|
-
if (
|
|
438
|
+
if (config.authorize) return config.authorize(ctx, resource, op);
|
|
424
439
|
return true;
|
|
425
440
|
}
|
|
426
441
|
};
|
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.8.0",
|
|
5
5
|
"description": "tRPC server utilities for auto-crud - automatic CRUD routers for Drizzle ORM",
|
|
6
6
|
"author": "wordrhyme",
|
|
7
7
|
"license": "MIT",
|
|
@@ -47,11 +47,11 @@
|
|
|
47
47
|
"typescript": "^5.9.3",
|
|
48
48
|
"vitest": "^4.0.18",
|
|
49
49
|
"zod": "^4.1.13",
|
|
50
|
+
"@internal/eslint-config": "0.3.0",
|
|
50
51
|
"@internal/prettier-config": "0.0.1",
|
|
51
52
|
"@internal/tsconfig": "0.1.0",
|
|
52
|
-
"@internal/tsdown-config": "0.1.0",
|
|
53
53
|
"@internal/vitest-config": "0.1.0",
|
|
54
|
-
"@internal/
|
|
54
|
+
"@internal/tsdown-config": "0.1.0"
|
|
55
55
|
},
|
|
56
56
|
"prettier": "@internal/prettier-config",
|
|
57
57
|
"scripts": {
|