@wordrhyme/auto-crud-server 1.1.5 → 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 +14 -11
- package/dist/index.cjs +41 -13
- package/dist/index.d.cts +8 -8
- package/dist/index.d.ts +1 -1
- package/dist/index.js +41 -13
- package/package.json +3 -3
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
|
|
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
|
-
//
|
|
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`
|
|
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: ['
|
|
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: ['
|
|
998
|
+
// 自动派生 schema 时额外排除这些字段;默认托管字段无需重复写
|
|
999
|
+
omitFields: ['internalCode'],
|
|
997
1000
|
});
|
|
998
1001
|
```
|
|
999
1002
|
|
package/dist/index.cjs
CHANGED
|
@@ -432,6 +432,9 @@ function nonEmpty(value) {
|
|
|
432
432
|
|
|
433
433
|
//#endregion
|
|
434
434
|
//#region src/routers/_factory.ts
|
|
435
|
+
function normalizePluginRouteId(pluginId) {
|
|
436
|
+
return pluginId.replace(/^com\.wordrhyme\./, "").replace(/\./g, "-");
|
|
437
|
+
}
|
|
435
438
|
function resolveSoftDelete(option) {
|
|
436
439
|
if (!option) return null;
|
|
437
440
|
if (option === true) return {
|
|
@@ -559,7 +562,11 @@ const importInputSchema = zod.z.object({
|
|
|
559
562
|
const DEFAULT_OMIT_FIELDS = [
|
|
560
563
|
"id",
|
|
561
564
|
"createdAt",
|
|
562
|
-
"updatedAt"
|
|
565
|
+
"updatedAt",
|
|
566
|
+
"createdBy",
|
|
567
|
+
"createdByType",
|
|
568
|
+
"updatedBy",
|
|
569
|
+
"updatedByType"
|
|
563
570
|
];
|
|
564
571
|
function shouldEnableCrudExtensionSchema(config) {
|
|
565
572
|
return Boolean(config.id) && config.extensions !== false;
|
|
@@ -573,19 +580,30 @@ function withCrudExtensionInput(schema, config) {
|
|
|
573
580
|
* - 优先使用显式传入的 Schema
|
|
574
581
|
* - 否则从 Drizzle table 自动派生
|
|
575
582
|
*/
|
|
576
|
-
function resolveSchemas(config) {
|
|
577
|
-
const { table
|
|
583
|
+
function resolveSchemas(config, idField = "id") {
|
|
584
|
+
const { table } = config;
|
|
585
|
+
const omitFields = [...new Set([...DEFAULT_OMIT_FIELDS, ...config.omitFields ?? []])];
|
|
578
586
|
let schema;
|
|
587
|
+
let upsertSchema;
|
|
588
|
+
let importSchema;
|
|
579
589
|
if (config.schema) {
|
|
580
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.");
|
|
581
591
|
schema = config.schema;
|
|
592
|
+
upsertSchema = schema;
|
|
593
|
+
importSchema = schema;
|
|
582
594
|
} else {
|
|
583
595
|
const rawSchema = (0, drizzle_zod.createInsertSchema)(table);
|
|
584
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.");
|
|
585
|
-
const omitConfig = Object.fromEntries(omitFields.map((
|
|
597
|
+
const omitConfig = Object.fromEntries(omitFields.filter((field) => field in rawSchema.shape).map((field) => [field, true]));
|
|
586
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;
|
|
587
603
|
}
|
|
588
604
|
schema = withCrudExtensionInput(schema, config);
|
|
605
|
+
upsertSchema = withCrudExtensionInput(upsertSchema, config);
|
|
606
|
+
importSchema = withCrudExtensionInput(importSchema, config);
|
|
589
607
|
let selectSchema;
|
|
590
608
|
if (config.selectSchema) selectSchema = config.selectSchema;
|
|
591
609
|
else if (config.schema) selectSchema = schema;
|
|
@@ -594,6 +612,8 @@ function resolveSchemas(config) {
|
|
|
594
612
|
return {
|
|
595
613
|
selectSchema,
|
|
596
614
|
schema,
|
|
615
|
+
upsertSchema,
|
|
616
|
+
importSchema,
|
|
597
617
|
updateSchema
|
|
598
618
|
};
|
|
599
619
|
}
|
|
@@ -648,9 +668,14 @@ function readCrudTarget(config) {
|
|
|
648
668
|
if (!id) return null;
|
|
649
669
|
return { id };
|
|
650
670
|
}
|
|
651
|
-
function buildCrudLifecycleHookId(config, operation) {
|
|
671
|
+
function buildCrudLifecycleHookId(config, operation, ctx) {
|
|
652
672
|
const target = readCrudTarget(config);
|
|
653
673
|
if (!target) return null;
|
|
674
|
+
const pluginId = ctx?.pluginId;
|
|
675
|
+
if (typeof pluginId === "string" && target.id.startsWith(`${pluginId}.`)) {
|
|
676
|
+
const resourceId = target.id.slice(pluginId.length + 1);
|
|
677
|
+
if (resourceId) return `${normalizePluginRouteId(pluginId)}.${resourceId}.${operation}`;
|
|
678
|
+
}
|
|
654
679
|
return `${target.id}.${operation}`;
|
|
655
680
|
}
|
|
656
681
|
function getCrudLifecycleHooks(ctx) {
|
|
@@ -658,10 +683,10 @@ function getCrudLifecycleHooks(ctx) {
|
|
|
658
683
|
return typeof hooks?.emit === "function" ? hooks : null;
|
|
659
684
|
}
|
|
660
685
|
function shouldWrapCrudLifecycleTransaction(ctx, config) {
|
|
661
|
-
return Boolean(buildCrudLifecycleHookId(config, "create") && getCrudLifecycleHooks(ctx));
|
|
686
|
+
return Boolean(buildCrudLifecycleHookId(config, "create", ctx) && getCrudLifecycleHooks(ctx));
|
|
662
687
|
}
|
|
663
688
|
async function emitCrudWriteLifecycle(ctx, config, operation, payload) {
|
|
664
|
-
const hookId = buildCrudLifecycleHookId(config, operation);
|
|
689
|
+
const hookId = buildCrudLifecycleHookId(config, operation, ctx);
|
|
665
690
|
const hooks = getCrudLifecycleHooks(ctx);
|
|
666
691
|
if (!hookId || !hooks) return;
|
|
667
692
|
await hooks.emit(hookId, payload, {
|
|
@@ -919,7 +944,7 @@ function combineConditions(...conditions) {
|
|
|
919
944
|
*/
|
|
920
945
|
function createCrudRouter(config) {
|
|
921
946
|
const { table, idField = "id", filterableColumns, searchColumns, sortableColumns, maxBatchSize = 100, maxExportSize = 5e3, softDelete: softDeleteOption, middleware = {} } = config;
|
|
922
|
-
const { selectSchema, schema, updateSchema } = resolveSchemas(config);
|
|
947
|
+
const { selectSchema, schema, upsertSchema, importSchema, updateSchema } = resolveSchemas(config, idField);
|
|
923
948
|
const entityOutputSchema = withCrudExtensionInput(selectSchema, config);
|
|
924
949
|
const listOutputSchema = zod.z.object({
|
|
925
950
|
data: zod.z.array(entityOutputSchema),
|
|
@@ -1039,9 +1064,12 @@ function createCrudRouter(config) {
|
|
|
1039
1064
|
})));
|
|
1040
1065
|
let query = ctx.db.select().from(table).$dynamic();
|
|
1041
1066
|
if (where) query = query.where(where);
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
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)) {
|
|
1045
1073
|
const column = resolveColumnTarget({
|
|
1046
1074
|
table,
|
|
1047
1075
|
columnId: sortField.id,
|
|
@@ -1247,7 +1275,7 @@ function createCrudRouter(config) {
|
|
|
1247
1275
|
});
|
|
1248
1276
|
return doUpdateMany(input.data);
|
|
1249
1277
|
}),
|
|
1250
|
-
upsert: resolved.procedureFactory("upsert").input(
|
|
1278
|
+
upsert: resolved.procedureFactory("upsert").input(upsertSchema).output(upsertOutputSchema).mutation(async ({ ctx, input }) => {
|
|
1251
1279
|
await withGuard(ctx, "upsert");
|
|
1252
1280
|
const doUpsert = async (inputData) => {
|
|
1253
1281
|
const splitInput = splitCrudExtensionWriteInput(inputData, tableColumnNames);
|
|
@@ -1392,7 +1420,7 @@ function createCrudRouter(config) {
|
|
|
1392
1420
|
const failed = [];
|
|
1393
1421
|
for (let i = 0; i < importData$1.rows.length; i++) {
|
|
1394
1422
|
const row = importData$1.rows[i];
|
|
1395
|
-
const result =
|
|
1423
|
+
const result = importSchema.safeParse(row);
|
|
1396
1424
|
if (result.success) {
|
|
1397
1425
|
const splitRow = splitCrudExtensionWriteInput(result.data, tableColumnNames);
|
|
1398
1426
|
validRows.push(splitRow);
|
package/dist/index.d.cts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
2
|
import { PgTable } from "drizzle-orm/pg-core";
|
|
3
|
-
import * as
|
|
3
|
+
import * as _trpc_server2 from "@trpc/server";
|
|
4
4
|
import { PostgresJsDatabase } from "drizzle-orm/postgres-js";
|
|
5
5
|
import { AnyColumn, SQL } from "drizzle-orm";
|
|
6
6
|
|
|
@@ -12,13 +12,13 @@ import { AnyColumn, SQL } from "drizzle-orm";
|
|
|
12
12
|
interface Context {
|
|
13
13
|
db: PostgresJsDatabase<Record<string, never>>;
|
|
14
14
|
}
|
|
15
|
-
declare const router:
|
|
15
|
+
declare const router: _trpc_server2.TRPCRouterBuilder<{
|
|
16
16
|
ctx: Context;
|
|
17
17
|
meta: object;
|
|
18
|
-
errorShape:
|
|
18
|
+
errorShape: _trpc_server2.TRPCDefaultErrorShape;
|
|
19
19
|
transformer: true;
|
|
20
20
|
}>;
|
|
21
|
-
declare const publicProcedure:
|
|
21
|
+
declare const publicProcedure: _trpc_server2.TRPCProcedureBuilder<Context, object, object, _trpc_server2.TRPCUnsetMarker, _trpc_server2.TRPCUnsetMarker, _trpc_server2.TRPCUnsetMarker, _trpc_server2.TRPCUnsetMarker, false>;
|
|
22
22
|
//#endregion
|
|
23
23
|
//#region src/types/config.d.ts
|
|
24
24
|
type CrudOperation = 'list' | 'get' | 'create' | 'update' | 'delete' | 'deleteMany' | 'updateMany' | 'upsert' | 'export' | 'import' | 'createMany';
|
|
@@ -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" */
|
|
@@ -890,12 +890,12 @@ type CrudHookEventMap<PluginId extends string, ResourceName extends string, TCre
|
|
|
890
890
|
} } & { [K in `${PluginId}.${ResourceName}.createMany`]: TCreate[] };
|
|
891
891
|
//#endregion
|
|
892
892
|
//#region src/routers/index.d.ts
|
|
893
|
-
declare const appRouter:
|
|
893
|
+
declare const appRouter: _trpc_server2.TRPCBuiltRouter<{
|
|
894
894
|
ctx: Context;
|
|
895
895
|
meta: object;
|
|
896
|
-
errorShape:
|
|
896
|
+
errorShape: _trpc_server2.TRPCDefaultErrorShape;
|
|
897
897
|
transformer: true;
|
|
898
|
-
},
|
|
898
|
+
}, _trpc_server2.TRPCDecorateCreateRouterOptions<{}>>;
|
|
899
899
|
type AppRouter = typeof appRouter;
|
|
900
900
|
//#endregion
|
|
901
901
|
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.d.ts
CHANGED
|
@@ -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" */
|
package/dist/index.js
CHANGED
|
@@ -403,6 +403,9 @@ function nonEmpty(value) {
|
|
|
403
403
|
|
|
404
404
|
//#endregion
|
|
405
405
|
//#region src/routers/_factory.ts
|
|
406
|
+
function normalizePluginRouteId(pluginId) {
|
|
407
|
+
return pluginId.replace(/^com\.wordrhyme\./, "").replace(/\./g, "-");
|
|
408
|
+
}
|
|
406
409
|
function resolveSoftDelete(option) {
|
|
407
410
|
if (!option) return null;
|
|
408
411
|
if (option === true) return {
|
|
@@ -530,7 +533,11 @@ const importInputSchema = z.object({
|
|
|
530
533
|
const DEFAULT_OMIT_FIELDS = [
|
|
531
534
|
"id",
|
|
532
535
|
"createdAt",
|
|
533
|
-
"updatedAt"
|
|
536
|
+
"updatedAt",
|
|
537
|
+
"createdBy",
|
|
538
|
+
"createdByType",
|
|
539
|
+
"updatedBy",
|
|
540
|
+
"updatedByType"
|
|
534
541
|
];
|
|
535
542
|
function shouldEnableCrudExtensionSchema(config) {
|
|
536
543
|
return Boolean(config.id) && config.extensions !== false;
|
|
@@ -544,19 +551,30 @@ function withCrudExtensionInput(schema, config) {
|
|
|
544
551
|
* - 优先使用显式传入的 Schema
|
|
545
552
|
* - 否则从 Drizzle table 自动派生
|
|
546
553
|
*/
|
|
547
|
-
function resolveSchemas(config) {
|
|
548
|
-
const { table
|
|
554
|
+
function resolveSchemas(config, idField = "id") {
|
|
555
|
+
const { table } = config;
|
|
556
|
+
const omitFields = [...new Set([...DEFAULT_OMIT_FIELDS, ...config.omitFields ?? []])];
|
|
549
557
|
let schema;
|
|
558
|
+
let upsertSchema;
|
|
559
|
+
let importSchema;
|
|
550
560
|
if (config.schema) {
|
|
551
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.");
|
|
552
562
|
schema = config.schema;
|
|
563
|
+
upsertSchema = schema;
|
|
564
|
+
importSchema = schema;
|
|
553
565
|
} else {
|
|
554
566
|
const rawSchema = createInsertSchema(table);
|
|
555
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.");
|
|
556
|
-
const omitConfig = Object.fromEntries(omitFields.map((
|
|
568
|
+
const omitConfig = Object.fromEntries(omitFields.filter((field) => field in rawSchema.shape).map((field) => [field, true]));
|
|
557
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;
|
|
558
574
|
}
|
|
559
575
|
schema = withCrudExtensionInput(schema, config);
|
|
576
|
+
upsertSchema = withCrudExtensionInput(upsertSchema, config);
|
|
577
|
+
importSchema = withCrudExtensionInput(importSchema, config);
|
|
560
578
|
let selectSchema;
|
|
561
579
|
if (config.selectSchema) selectSchema = config.selectSchema;
|
|
562
580
|
else if (config.schema) selectSchema = schema;
|
|
@@ -565,6 +583,8 @@ function resolveSchemas(config) {
|
|
|
565
583
|
return {
|
|
566
584
|
selectSchema,
|
|
567
585
|
schema,
|
|
586
|
+
upsertSchema,
|
|
587
|
+
importSchema,
|
|
568
588
|
updateSchema
|
|
569
589
|
};
|
|
570
590
|
}
|
|
@@ -619,9 +639,14 @@ function readCrudTarget(config) {
|
|
|
619
639
|
if (!id) return null;
|
|
620
640
|
return { id };
|
|
621
641
|
}
|
|
622
|
-
function buildCrudLifecycleHookId(config, operation) {
|
|
642
|
+
function buildCrudLifecycleHookId(config, operation, ctx) {
|
|
623
643
|
const target = readCrudTarget(config);
|
|
624
644
|
if (!target) return null;
|
|
645
|
+
const pluginId = ctx?.pluginId;
|
|
646
|
+
if (typeof pluginId === "string" && target.id.startsWith(`${pluginId}.`)) {
|
|
647
|
+
const resourceId = target.id.slice(pluginId.length + 1);
|
|
648
|
+
if (resourceId) return `${normalizePluginRouteId(pluginId)}.${resourceId}.${operation}`;
|
|
649
|
+
}
|
|
625
650
|
return `${target.id}.${operation}`;
|
|
626
651
|
}
|
|
627
652
|
function getCrudLifecycleHooks(ctx) {
|
|
@@ -629,10 +654,10 @@ function getCrudLifecycleHooks(ctx) {
|
|
|
629
654
|
return typeof hooks?.emit === "function" ? hooks : null;
|
|
630
655
|
}
|
|
631
656
|
function shouldWrapCrudLifecycleTransaction(ctx, config) {
|
|
632
|
-
return Boolean(buildCrudLifecycleHookId(config, "create") && getCrudLifecycleHooks(ctx));
|
|
657
|
+
return Boolean(buildCrudLifecycleHookId(config, "create", ctx) && getCrudLifecycleHooks(ctx));
|
|
633
658
|
}
|
|
634
659
|
async function emitCrudWriteLifecycle(ctx, config, operation, payload) {
|
|
635
|
-
const hookId = buildCrudLifecycleHookId(config, operation);
|
|
660
|
+
const hookId = buildCrudLifecycleHookId(config, operation, ctx);
|
|
636
661
|
const hooks = getCrudLifecycleHooks(ctx);
|
|
637
662
|
if (!hookId || !hooks) return;
|
|
638
663
|
await hooks.emit(hookId, payload, {
|
|
@@ -890,7 +915,7 @@ function combineConditions(...conditions) {
|
|
|
890
915
|
*/
|
|
891
916
|
function createCrudRouter(config) {
|
|
892
917
|
const { table, idField = "id", filterableColumns, searchColumns, sortableColumns, maxBatchSize = 100, maxExportSize = 5e3, softDelete: softDeleteOption, middleware = {} } = config;
|
|
893
|
-
const { selectSchema, schema, updateSchema } = resolveSchemas(config);
|
|
918
|
+
const { selectSchema, schema, upsertSchema, importSchema, updateSchema } = resolveSchemas(config, idField);
|
|
894
919
|
const entityOutputSchema = withCrudExtensionInput(selectSchema, config);
|
|
895
920
|
const listOutputSchema = z.object({
|
|
896
921
|
data: z.array(entityOutputSchema),
|
|
@@ -1010,9 +1035,12 @@ function createCrudRouter(config) {
|
|
|
1010
1035
|
})));
|
|
1011
1036
|
let query = ctx.db.select().from(table).$dynamic();
|
|
1012
1037
|
if (where) query = query.where(where);
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
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)) {
|
|
1016
1044
|
const column = resolveColumnTarget({
|
|
1017
1045
|
table,
|
|
1018
1046
|
columnId: sortField.id,
|
|
@@ -1218,7 +1246,7 @@ function createCrudRouter(config) {
|
|
|
1218
1246
|
});
|
|
1219
1247
|
return doUpdateMany(input.data);
|
|
1220
1248
|
}),
|
|
1221
|
-
upsert: resolved.procedureFactory("upsert").input(
|
|
1249
|
+
upsert: resolved.procedureFactory("upsert").input(upsertSchema).output(upsertOutputSchema).mutation(async ({ ctx, input }) => {
|
|
1222
1250
|
await withGuard(ctx, "upsert");
|
|
1223
1251
|
const doUpsert = async (inputData) => {
|
|
1224
1252
|
const splitInput = splitCrudExtensionWriteInput(inputData, tableColumnNames);
|
|
@@ -1363,7 +1391,7 @@ function createCrudRouter(config) {
|
|
|
1363
1391
|
const failed = [];
|
|
1364
1392
|
for (let i = 0; i < importData$1.rows.length; i++) {
|
|
1365
1393
|
const row = importData$1.rows[i];
|
|
1366
|
-
const result =
|
|
1394
|
+
const result = importSchema.safeParse(row);
|
|
1367
1395
|
if (result.success) {
|
|
1368
1396
|
const splitRow = splitCrudExtensionWriteInput(result.data, tableColumnNames);
|
|
1369
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.
|
|
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",
|
|
@@ -49,9 +49,9 @@
|
|
|
49
49
|
"vitest": "^4.0.18",
|
|
50
50
|
"zod": "^4.3.6",
|
|
51
51
|
"@internal/eslint-config": "0.3.0",
|
|
52
|
-
"@internal/tsdown-config": "0.1.0",
|
|
53
|
-
"@internal/vitest-config": "0.1.0",
|
|
54
52
|
"@internal/tsconfig": "0.1.0",
|
|
53
|
+
"@internal/vitest-config": "0.1.0",
|
|
54
|
+
"@internal/tsdown-config": "0.1.0",
|
|
55
55
|
"@internal/prettier-config": "0.0.1"
|
|
56
56
|
},
|
|
57
57
|
"prettier": "@internal/prettier-config",
|