@wordrhyme/auto-crud-server 1.1.6 → 1.1.8

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
@@ -83,7 +83,7 @@ import { tasks } from '@/db/schema';
83
83
  // 🚀 零配置!一行代码生成完整 CRUD 路由
84
84
  export const tasksRouter = createCrudRouter({
85
85
  table: tasks,
86
- // Schema 自动从 table 派生,排除 id, createdAt, updatedAt
86
+ // Schema 自动从 table 派生,排除平台托管字段
87
87
  });
88
88
  ```
89
89
 
@@ -169,6 +169,9 @@ export { handler as GET, handler as POST };
169
169
  }
170
170
  ```
171
171
 
172
+ 未传 `sort` 或传空数组时,如果表存在 `createdAt` 且未被 `sortableColumns`
173
+ 白名单排除,列表默认按 `createdAt` 降序返回。
174
+
172
175
  **输出**:
173
176
 
174
177
  ```typescript
@@ -491,18 +494,18 @@ interface CrudRouterConfig<TTable, TSelect, TInsert, TUpdate> {
491
494
  idField?: string; // ID 字段名,默认 "id"
492
495
  filterableColumns?: CrudColumnRef<TTable>[]; // 可过滤字段白名单
493
496
  sortableColumns?: CrudColumnRef<TTable>[]; // 可排序字段白名单
494
- omitFields?: string[]; // 自动派生时排除的字段
495
- // 默认 ["id", "createdAt", "updatedAt"]
497
+ omitFields?: string[]; // 自动派生时额外排除的业务字段
498
+ // 默认已排除 ["id", "createdAt", "updatedAt", "createdBy", "createdByType", "updatedBy", "updatedByType"]
496
499
  }
497
500
  ```
498
501
 
499
502
  #### Schema 派生规则
500
503
 
501
- | 配置 | 派生行为 |
502
- | ----------------- | ------------------------------------------- |
503
- | 无 `schema` | 从 `table` 自动派生,排除 `omitFields` |
504
- | 无 `updateSchema` | 从 `schema.partial().refine(nonEmpty)` 派生 |
505
- | 无 `selectSchema` | 若有 `schema` 则使用它,否则从 `table` 派生 |
504
+ | 配置 | 派生行为 |
505
+ | ----------------- | ---------------------------------------------------- |
506
+ | 无 `schema` | 从 `table` 自动派生,排除默认托管字段和 `omitFields` |
507
+ | 无 `updateSchema` | 从 `schema.partial().refine(nonEmpty)` 派生 |
508
+ | 无 `selectSchema` | 若有 `schema` 则使用它,否则从 `table` 派生 |
506
509
 
507
510
  #### 使用示例
508
511
 
@@ -528,7 +531,7 @@ const usersRouter = createCrudRouter({
528
531
  // 4. 自定义排除字段
529
532
  const ordersRouter = createCrudRouter({
530
533
  table: orders,
531
- omitFields: ['id', 'createdAt', 'updatedAt', 'internalCode'],
534
+ omitFields: ['internalCode'],
532
535
  });
533
536
  ```
534
537
 
@@ -992,8 +995,8 @@ export const usersRouter = createCrudRouter({
992
995
  ```typescript
993
996
  export const ordersRouter = createCrudRouter({
994
997
  table: orders,
995
- // 自动派生 schema 时排除这些字段
996
- omitFields: ['id', 'createdAt', 'updatedAt', 'internalCode'],
998
+ // 自动派生 schema 时额外排除这些字段;默认托管字段无需重复写
999
+ omitFields: ['internalCode'],
997
1000
  });
998
1001
  ```
999
1002
 
package/dist/index.cjs CHANGED
@@ -562,7 +562,11 @@ const importInputSchema = zod.z.object({
562
562
  const DEFAULT_OMIT_FIELDS = [
563
563
  "id",
564
564
  "createdAt",
565
- "updatedAt"
565
+ "updatedAt",
566
+ "createdBy",
567
+ "createdByType",
568
+ "updatedBy",
569
+ "updatedByType"
566
570
  ];
567
571
  function shouldEnableCrudExtensionSchema(config) {
568
572
  return Boolean(config.id) && config.extensions !== false;
@@ -576,19 +580,30 @@ function withCrudExtensionInput(schema, config) {
576
580
  * - 优先使用显式传入的 Schema
577
581
  * - 否则从 Drizzle table 自动派生
578
582
  */
579
- function resolveSchemas(config) {
580
- const { table, omitFields = DEFAULT_OMIT_FIELDS } = config;
583
+ function resolveSchemas(config, idField = "id") {
584
+ const { table } = config;
585
+ const omitFields = [...new Set([...DEFAULT_OMIT_FIELDS, ...config.omitFields ?? []])];
581
586
  let schema;
587
+ let upsertSchema;
588
+ let importSchema;
582
589
  if (config.schema) {
583
590
  if (!isZodObject(config.schema)) throw new Error("[createCrudRouter] schema must be a ZodObject (not ZodEffects or other types). If you're using .refine() or .transform(), please remove them from schema.");
584
591
  schema = config.schema;
592
+ upsertSchema = schema;
593
+ importSchema = schema;
585
594
  } else {
586
595
  const rawSchema = (0, drizzle_zod.createInsertSchema)(table);
587
596
  if (!isZodObject(rawSchema)) throw new Error("[createCrudRouter] drizzle-zod returned non-ZodObject schema. This may be due to table refinements. Please provide schema explicitly.");
588
- const omitConfig = Object.fromEntries(omitFields.map((f) => [f, true]));
597
+ const omitConfig = Object.fromEntries(omitFields.filter((field) => field in rawSchema.shape).map((field) => [field, true]));
589
598
  schema = rawSchema.omit(omitConfig);
599
+ const idSchema = rawSchema.shape[idField];
600
+ const schemaWithOptionalId = idSchema && !(idField in schema.shape) ? schema.extend({ [idField]: idSchema.optional() }) : schema;
601
+ upsertSchema = schemaWithOptionalId;
602
+ importSchema = schemaWithOptionalId;
590
603
  }
591
604
  schema = withCrudExtensionInput(schema, config);
605
+ upsertSchema = withCrudExtensionInput(upsertSchema, config);
606
+ importSchema = withCrudExtensionInput(importSchema, config);
592
607
  let selectSchema;
593
608
  if (config.selectSchema) selectSchema = config.selectSchema;
594
609
  else if (config.schema) selectSchema = schema;
@@ -597,6 +612,8 @@ function resolveSchemas(config) {
597
612
  return {
598
613
  selectSchema,
599
614
  schema,
615
+ upsertSchema,
616
+ importSchema,
600
617
  updateSchema
601
618
  };
602
619
  }
@@ -927,7 +944,7 @@ function combineConditions(...conditions) {
927
944
  */
928
945
  function createCrudRouter(config) {
929
946
  const { table, idField = "id", filterableColumns, searchColumns, sortableColumns, maxBatchSize = 100, maxExportSize = 5e3, softDelete: softDeleteOption, middleware = {} } = config;
930
- const { selectSchema, schema, updateSchema } = resolveSchemas(config);
947
+ const { selectSchema, schema, upsertSchema, importSchema, updateSchema } = resolveSchemas(config, idField);
931
948
  const entityOutputSchema = withCrudExtensionInput(selectSchema, config);
932
949
  const listOutputSchema = zod.z.object({
933
950
  data: zod.z.array(entityOutputSchema),
@@ -1047,9 +1064,12 @@ function createCrudRouter(config) {
1047
1064
  })));
1048
1065
  let query = ctx.db.select().from(table).$dynamic();
1049
1066
  if (where) query = query.where(where);
1050
- if (effectiveInput.sort?.length) {
1051
- const sortField = effectiveInput.sort[0];
1052
- if (sortField && validateColumn(sortField.id, sortableColumns)) {
1067
+ const sortField = effectiveInput.sort?.[0] ?? (getTableColumn(table, "createdAt") ? {
1068
+ id: "createdAt",
1069
+ desc: true
1070
+ } : void 0);
1071
+ if (sortField) {
1072
+ if (validateColumn(sortField.id, sortableColumns)) {
1053
1073
  const column = resolveColumnTarget({
1054
1074
  table,
1055
1075
  columnId: sortField.id,
@@ -1255,7 +1275,7 @@ function createCrudRouter(config) {
1255
1275
  });
1256
1276
  return doUpdateMany(input.data);
1257
1277
  }),
1258
- upsert: resolved.procedureFactory("upsert").input(schema).output(upsertOutputSchema).mutation(async ({ ctx, input }) => {
1278
+ upsert: resolved.procedureFactory("upsert").input(upsertSchema).output(upsertOutputSchema).mutation(async ({ ctx, input }) => {
1259
1279
  await withGuard(ctx, "upsert");
1260
1280
  const doUpsert = async (inputData) => {
1261
1281
  const splitInput = splitCrudExtensionWriteInput(inputData, tableColumnNames);
@@ -1400,7 +1420,7 @@ function createCrudRouter(config) {
1400
1420
  const failed = [];
1401
1421
  for (let i = 0; i < importData$1.rows.length; i++) {
1402
1422
  const row = importData$1.rows[i];
1403
- const result = schema.safeParse(row);
1423
+ const result = importSchema.safeParse(row);
1404
1424
  if (result.success) {
1405
1425
  const splitRow = splitCrudExtensionWriteInput(result.data, tableColumnNames);
1406
1426
  validRows.push(splitRow);
package/dist/index.d.cts CHANGED
@@ -581,7 +581,7 @@ interface CrudRouterConfig<TTable extends PgTable = PgTable, TContext = unknown,
581
581
  authorize?: (ctx: TContext, resource: TSelect, operation: CrudOperation) => boolean | Promise<boolean>;
582
582
  /**
583
583
  * 要从 schema 排除的字段(自动派生时使用)
584
- * @default ["id", "createdAt", "updatedAt"]
584
+ * @default ["id", "createdAt", "updatedAt", "createdBy", "createdByType", "updatedBy", "updatedByType"]
585
585
  */
586
586
  omitFields?: TableColumnKeys<TTable>[];
587
587
  /** ID 字段名,默认 "id" */
package/dist/index.d.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  import { z } from "zod";
2
2
  import { AnyColumn, SQL } from "drizzle-orm";
3
- import * as _trpc_server0 from "@trpc/server";
3
+ import * as _trpc_server2 from "@trpc/server";
4
4
  import superjson from "superjson";
5
5
  import { PgTable } from "drizzle-orm/pg-core";
6
6
  import { PostgresJsDatabase } from "drizzle-orm/postgres-js";
@@ -13,13 +13,13 @@ import { PostgresJsDatabase } from "drizzle-orm/postgres-js";
13
13
  interface Context {
14
14
  db: PostgresJsDatabase<Record<string, never>>;
15
15
  }
16
- declare const router: _trpc_server0.TRPCRouterBuilder<{
16
+ declare const router: _trpc_server2.TRPCRouterBuilder<{
17
17
  ctx: Context;
18
18
  meta: object;
19
- errorShape: _trpc_server0.TRPCDefaultErrorShape;
19
+ errorShape: _trpc_server2.TRPCDefaultErrorShape;
20
20
  transformer: true;
21
21
  }>;
22
- declare const publicProcedure: _trpc_server0.TRPCProcedureBuilder<Context, object, object, _trpc_server0.TRPCUnsetMarker, _trpc_server0.TRPCUnsetMarker, _trpc_server0.TRPCUnsetMarker, _trpc_server0.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>;
23
23
  //#endregion
24
24
  //#region src/types/config.d.ts
25
25
  type CrudOperation = 'list' | 'get' | 'create' | 'update' | 'delete' | 'deleteMany' | 'updateMany' | 'upsert' | 'export' | 'import' | 'createMany';
@@ -582,7 +582,7 @@ interface CrudRouterConfig<TTable extends PgTable = PgTable, TContext = unknown,
582
582
  authorize?: (ctx: TContext, resource: TSelect, operation: CrudOperation) => boolean | Promise<boolean>;
583
583
  /**
584
584
  * 要从 schema 排除的字段(自动派生时使用)
585
- * @default ["id", "createdAt", "updatedAt"]
585
+ * @default ["id", "createdAt", "updatedAt", "createdBy", "createdByType", "updatedBy", "updatedByType"]
586
586
  */
587
587
  omitFields?: TableColumnKeys<TTable>[];
588
588
  /** ID 字段名,默认 "id" */
@@ -891,12 +891,12 @@ type CrudHookEventMap<PluginId extends string, ResourceName extends string, TCre
891
891
  } } & { [K in `${PluginId}.${ResourceName}.createMany`]: TCreate[] };
892
892
  //#endregion
893
893
  //#region src/routers/index.d.ts
894
- declare const appRouter: _trpc_server0.TRPCBuiltRouter<{
894
+ declare const appRouter: _trpc_server2.TRPCBuiltRouter<{
895
895
  ctx: Context;
896
896
  meta: object;
897
- errorShape: _trpc_server0.TRPCDefaultErrorShape;
897
+ errorShape: _trpc_server2.TRPCDefaultErrorShape;
898
898
  transformer: true;
899
- }, _trpc_server0.TRPCDecorateCreateRouterOptions<{}>>;
899
+ }, _trpc_server2.TRPCDecorateCreateRouterOptions<{}>>;
900
900
  type AppRouter = typeof appRouter;
901
901
  //#endregion
902
902
  export { type AnyProcedure, type AppRouter, type CreateManyMiddlewareParams, type CreateMiddlewareParams, type CrudColumnConfig, type CrudColumnExpression, type CrudColumnRef, type CrudExtensionFilter, type CrudExtensionMetadata, type CrudExtensionsConfig, type CrudExtensionsProvider, type CrudHookEventMap, type CrudMiddleware, type CrudOperation, type CrudProcedures, type CrudRouterConfig, type DeleteManyMiddlewareParams, type DeleteMiddlewareParams, type ExportInput, type ExportMiddlewareParams, type ExportResult, type GetInput, type GetMiddlewareParams, type ImportFailedRow, type ImportInput, type ImportMiddlewareParams, type ImportResult, 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, baseExportInputSchema, baseGetInputSchema, baseListInputSchema, beforeMiddleware, composeMiddleware, createCrudRouter, publicProcedure, router };
package/dist/index.js CHANGED
@@ -533,7 +533,11 @@ const importInputSchema = z.object({
533
533
  const DEFAULT_OMIT_FIELDS = [
534
534
  "id",
535
535
  "createdAt",
536
- "updatedAt"
536
+ "updatedAt",
537
+ "createdBy",
538
+ "createdByType",
539
+ "updatedBy",
540
+ "updatedByType"
537
541
  ];
538
542
  function shouldEnableCrudExtensionSchema(config) {
539
543
  return Boolean(config.id) && config.extensions !== false;
@@ -547,19 +551,30 @@ function withCrudExtensionInput(schema, config) {
547
551
  * - 优先使用显式传入的 Schema
548
552
  * - 否则从 Drizzle table 自动派生
549
553
  */
550
- function resolveSchemas(config) {
551
- const { table, omitFields = DEFAULT_OMIT_FIELDS } = config;
554
+ function resolveSchemas(config, idField = "id") {
555
+ const { table } = config;
556
+ const omitFields = [...new Set([...DEFAULT_OMIT_FIELDS, ...config.omitFields ?? []])];
552
557
  let schema;
558
+ let upsertSchema;
559
+ let importSchema;
553
560
  if (config.schema) {
554
561
  if (!isZodObject(config.schema)) throw new Error("[createCrudRouter] schema must be a ZodObject (not ZodEffects or other types). If you're using .refine() or .transform(), please remove them from schema.");
555
562
  schema = config.schema;
563
+ upsertSchema = schema;
564
+ importSchema = schema;
556
565
  } else {
557
566
  const rawSchema = createInsertSchema(table);
558
567
  if (!isZodObject(rawSchema)) throw new Error("[createCrudRouter] drizzle-zod returned non-ZodObject schema. This may be due to table refinements. Please provide schema explicitly.");
559
- const omitConfig = Object.fromEntries(omitFields.map((f) => [f, true]));
568
+ const omitConfig = Object.fromEntries(omitFields.filter((field) => field in rawSchema.shape).map((field) => [field, true]));
560
569
  schema = rawSchema.omit(omitConfig);
570
+ const idSchema = rawSchema.shape[idField];
571
+ const schemaWithOptionalId = idSchema && !(idField in schema.shape) ? schema.extend({ [idField]: idSchema.optional() }) : schema;
572
+ upsertSchema = schemaWithOptionalId;
573
+ importSchema = schemaWithOptionalId;
561
574
  }
562
575
  schema = withCrudExtensionInput(schema, config);
576
+ upsertSchema = withCrudExtensionInput(upsertSchema, config);
577
+ importSchema = withCrudExtensionInput(importSchema, config);
563
578
  let selectSchema;
564
579
  if (config.selectSchema) selectSchema = config.selectSchema;
565
580
  else if (config.schema) selectSchema = schema;
@@ -568,6 +583,8 @@ function resolveSchemas(config) {
568
583
  return {
569
584
  selectSchema,
570
585
  schema,
586
+ upsertSchema,
587
+ importSchema,
571
588
  updateSchema
572
589
  };
573
590
  }
@@ -898,7 +915,7 @@ function combineConditions(...conditions) {
898
915
  */
899
916
  function createCrudRouter(config) {
900
917
  const { table, idField = "id", filterableColumns, searchColumns, sortableColumns, maxBatchSize = 100, maxExportSize = 5e3, softDelete: softDeleteOption, middleware = {} } = config;
901
- const { selectSchema, schema, updateSchema } = resolveSchemas(config);
918
+ const { selectSchema, schema, upsertSchema, importSchema, updateSchema } = resolveSchemas(config, idField);
902
919
  const entityOutputSchema = withCrudExtensionInput(selectSchema, config);
903
920
  const listOutputSchema = z.object({
904
921
  data: z.array(entityOutputSchema),
@@ -1018,9 +1035,12 @@ function createCrudRouter(config) {
1018
1035
  })));
1019
1036
  let query = ctx.db.select().from(table).$dynamic();
1020
1037
  if (where) query = query.where(where);
1021
- if (effectiveInput.sort?.length) {
1022
- const sortField = effectiveInput.sort[0];
1023
- if (sortField && validateColumn(sortField.id, sortableColumns)) {
1038
+ const sortField = effectiveInput.sort?.[0] ?? (getTableColumn(table, "createdAt") ? {
1039
+ id: "createdAt",
1040
+ desc: true
1041
+ } : void 0);
1042
+ if (sortField) {
1043
+ if (validateColumn(sortField.id, sortableColumns)) {
1024
1044
  const column = resolveColumnTarget({
1025
1045
  table,
1026
1046
  columnId: sortField.id,
@@ -1226,7 +1246,7 @@ function createCrudRouter(config) {
1226
1246
  });
1227
1247
  return doUpdateMany(input.data);
1228
1248
  }),
1229
- upsert: resolved.procedureFactory("upsert").input(schema).output(upsertOutputSchema).mutation(async ({ ctx, input }) => {
1249
+ upsert: resolved.procedureFactory("upsert").input(upsertSchema).output(upsertOutputSchema).mutation(async ({ ctx, input }) => {
1230
1250
  await withGuard(ctx, "upsert");
1231
1251
  const doUpsert = async (inputData) => {
1232
1252
  const splitInput = splitCrudExtensionWriteInput(inputData, tableColumnNames);
@@ -1371,7 +1391,7 @@ function createCrudRouter(config) {
1371
1391
  const failed = [];
1372
1392
  for (let i = 0; i < importData$1.rows.length; i++) {
1373
1393
  const row = importData$1.rows[i];
1374
- const result = schema.safeParse(row);
1394
+ const result = importSchema.safeParse(row);
1375
1395
  if (result.success) {
1376
1396
  const splitRow = splitCrudExtensionWriteInput(result.data, tableColumnNames);
1377
1397
  validRows.push(splitRow);
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@wordrhyme/auto-crud-server",
3
3
  "type": "module",
4
- "version": "1.1.6",
4
+ "version": "1.1.8",
5
5
  "description": "tRPC server utilities for auto-crud - automatic CRUD routers for Drizzle ORM",
6
6
  "author": "wordrhyme",
7
7
  "license": "MIT",
@@ -48,11 +48,11 @@
48
48
  "typescript": "^5.9.3",
49
49
  "vitest": "^4.0.18",
50
50
  "zod": "^4.3.6",
51
+ "@internal/eslint-config": "0.3.0",
51
52
  "@internal/tsconfig": "0.1.0",
53
+ "@internal/vitest-config": "0.1.0",
52
54
  "@internal/tsdown-config": "0.1.0",
53
- "@internal/prettier-config": "0.0.1",
54
- "@internal/eslint-config": "0.3.0",
55
- "@internal/vitest-config": "0.1.0"
55
+ "@internal/prettier-config": "0.0.1"
56
56
  },
57
57
  "prettier": "@internal/prettier-config",
58
58
  "scripts": {