@wordrhyme/auto-crud-server 0.6.1 → 0.6.2
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 +37 -16
- package/dist/index.js +38 -17
- package/package.json +5 -3
package/dist/index.cjs
CHANGED
|
@@ -493,7 +493,7 @@ function createCrudRouter(config) {
|
|
|
493
493
|
const conditions = [];
|
|
494
494
|
const scopeCondition = resolved.getScope(ctx, table, operation);
|
|
495
495
|
if (scopeCondition) conditions.push(scopeCondition);
|
|
496
|
-
if (softDelete
|
|
496
|
+
if (softDelete) {
|
|
497
497
|
const col = getSoftDeleteColumn();
|
|
498
498
|
if (col) conditions.push((0, drizzle_orm.isNull)(col));
|
|
499
499
|
}
|
|
@@ -507,15 +507,24 @@ function createCrudRouter(config) {
|
|
|
507
507
|
if (operation === "upsert") {
|
|
508
508
|
const canCreate = await resolved.guard(ctx, "create");
|
|
509
509
|
const canUpdate = await resolved.guard(ctx, "update");
|
|
510
|
-
if (!canCreate || !canUpdate) throw new
|
|
510
|
+
if (!canCreate || !canUpdate) throw new __trpc_server.TRPCError({
|
|
511
|
+
code: "FORBIDDEN",
|
|
512
|
+
message: "Forbidden: upsert requires both create and update permissions"
|
|
513
|
+
});
|
|
511
514
|
return;
|
|
512
515
|
}
|
|
513
|
-
if (!await resolved.guard(ctx, operation)) throw new
|
|
516
|
+
if (!await resolved.guard(ctx, operation)) throw new __trpc_server.TRPCError({
|
|
517
|
+
code: "FORBIDDEN",
|
|
518
|
+
message: `Forbidden: ${operation} not allowed`
|
|
519
|
+
});
|
|
514
520
|
}
|
|
515
521
|
};
|
|
516
522
|
const withAuthorize = async (ctx, resource, operation) => {
|
|
517
523
|
if (!resource) return;
|
|
518
|
-
if (!await resolved.checkAuthorize(ctx, resource, operation)) throw new
|
|
524
|
+
if (!await resolved.checkAuthorize(ctx, resource, operation)) throw new __trpc_server.TRPCError({
|
|
525
|
+
code: "FORBIDDEN",
|
|
526
|
+
message: `Forbidden: Cannot ${operation} this resource`
|
|
527
|
+
});
|
|
519
528
|
};
|
|
520
529
|
const procedures = {
|
|
521
530
|
list: resolved.procedureFactory("list").input(listInputSchema).query(async ({ ctx, input }) => {
|
|
@@ -598,7 +607,10 @@ function createCrudRouter(config) {
|
|
|
598
607
|
const where = buildWhere(ctx, "update", (0, drizzle_orm.eq)(getIdColumn(), input.id));
|
|
599
608
|
const [existing] = await ctx.db.select().from(table).where(where);
|
|
600
609
|
await withAuthorize(ctx, existing, "update");
|
|
601
|
-
if (!existing) throw new
|
|
610
|
+
if (!existing) throw new __trpc_server.TRPCError({
|
|
611
|
+
code: "NOT_FOUND",
|
|
612
|
+
message: "Resource not found or access denied"
|
|
613
|
+
});
|
|
602
614
|
const doUpdate = async (updateData) => {
|
|
603
615
|
const injectData = resolved.getInject(ctx, "update");
|
|
604
616
|
const data = {
|
|
@@ -622,7 +634,10 @@ function createCrudRouter(config) {
|
|
|
622
634
|
const where = buildWhere(ctx, "delete", (0, drizzle_orm.eq)(getIdColumn(), input));
|
|
623
635
|
const [existing] = await ctx.db.select().from(table).where(where);
|
|
624
636
|
await withAuthorize(ctx, existing, "delete");
|
|
625
|
-
if (!existing) throw new
|
|
637
|
+
if (!existing) throw new __trpc_server.TRPCError({
|
|
638
|
+
code: "NOT_FOUND",
|
|
639
|
+
message: "Resource not found or access denied"
|
|
640
|
+
});
|
|
626
641
|
const doDelete = async () => {
|
|
627
642
|
let deleted;
|
|
628
643
|
if (softDelete) [deleted] = await ctx.db.update(table).set({ [softDelete.column]: softDelete.getValue() }).where(where).returning();
|
|
@@ -641,8 +656,8 @@ function createCrudRouter(config) {
|
|
|
641
656
|
await withGuard(ctx, "deleteMany");
|
|
642
657
|
const where = buildWhere(ctx, "deleteMany", (0, drizzle_orm.inArray)(getIdColumn(), input));
|
|
643
658
|
const doDeleteMany = async () => {
|
|
644
|
-
if (softDelete) return { deleted: (await ctx.db.update(table).set({ [softDelete.column]: softDelete.getValue() }).where(where).returning()).length };
|
|
645
|
-
else return { deleted: (await ctx.db.delete(table).where(where).returning()).length };
|
|
659
|
+
if (softDelete) return { deleted: (await ctx.db.update(table).set({ [softDelete.column]: softDelete.getValue() }).where(where).returning({ id: getIdColumn() })).length };
|
|
660
|
+
else return { deleted: (await ctx.db.delete(table).where(where).returning({ id: getIdColumn() })).length };
|
|
646
661
|
};
|
|
647
662
|
if (m.deleteMany) return m.deleteMany({
|
|
648
663
|
ctx,
|
|
@@ -663,7 +678,7 @@ function createCrudRouter(config) {
|
|
|
663
678
|
...updateData,
|
|
664
679
|
...injectData
|
|
665
680
|
};
|
|
666
|
-
return { updated: (await ctx.db.update(table).set(data).where(where).returning()).length };
|
|
681
|
+
return { updated: (await ctx.db.update(table).set(data).where(where).returning({ id: getIdColumn() })).length };
|
|
667
682
|
};
|
|
668
683
|
if (m.updateMany) return m.updateMany({
|
|
669
684
|
ctx,
|
|
@@ -676,21 +691,27 @@ function createCrudRouter(config) {
|
|
|
676
691
|
upsert: resolved.procedureFactory("upsert").input(insertSchema).mutation(async ({ ctx, input }) => {
|
|
677
692
|
await withGuard(ctx, "upsert");
|
|
678
693
|
const doUpsert = async (inputData) => {
|
|
679
|
-
const injectData = resolved.getInject(ctx, "create");
|
|
680
|
-
const data = {
|
|
681
|
-
...inputData,
|
|
682
|
-
...injectData
|
|
683
|
-
};
|
|
684
694
|
const inputId = inputData[idField];
|
|
685
695
|
let isNew = true;
|
|
696
|
+
let existing = null;
|
|
686
697
|
if (inputId) {
|
|
687
698
|
const where = buildWhere(ctx, "get", (0, drizzle_orm.eq)(getIdColumn(), inputId));
|
|
688
|
-
const [
|
|
699
|
+
const [found] = await ctx.db.select().from(table).where(where);
|
|
700
|
+
existing = found;
|
|
689
701
|
isNew = !existing;
|
|
702
|
+
if (!isNew && existing) await withAuthorize(ctx, existing, "update");
|
|
690
703
|
}
|
|
704
|
+
const injectData = resolved.getInject(ctx, isNew ? "create" : "update");
|
|
705
|
+
const data = {
|
|
706
|
+
...inputData,
|
|
707
|
+
...injectData
|
|
708
|
+
};
|
|
691
709
|
const [result] = await ctx.db.insert(table).values(data).onConflictDoUpdate({
|
|
692
710
|
target: getIdColumn(),
|
|
693
|
-
set:
|
|
711
|
+
set: {
|
|
712
|
+
...inputData,
|
|
713
|
+
...resolved.getInject(ctx, "update")
|
|
714
|
+
}
|
|
694
715
|
}).returning();
|
|
695
716
|
return {
|
|
696
717
|
data: result,
|
package/dist/index.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
2
|
import { and, asc, desc, eq, gt, gte, ilike, inArray, isNull, lt, lte, ne, not, notIlike, notInArray, or, sql } from "drizzle-orm";
|
|
3
|
-
import { initTRPC } from "@trpc/server";
|
|
3
|
+
import { TRPCError, initTRPC } from "@trpc/server";
|
|
4
4
|
import superjson from "superjson";
|
|
5
5
|
import { addDays, endOfDay, startOfDay } from "date-fns";
|
|
6
6
|
|
|
@@ -465,7 +465,7 @@ function createCrudRouter(config) {
|
|
|
465
465
|
const conditions = [];
|
|
466
466
|
const scopeCondition = resolved.getScope(ctx, table, operation);
|
|
467
467
|
if (scopeCondition) conditions.push(scopeCondition);
|
|
468
|
-
if (softDelete
|
|
468
|
+
if (softDelete) {
|
|
469
469
|
const col = getSoftDeleteColumn();
|
|
470
470
|
if (col) conditions.push(isNull(col));
|
|
471
471
|
}
|
|
@@ -479,15 +479,24 @@ function createCrudRouter(config) {
|
|
|
479
479
|
if (operation === "upsert") {
|
|
480
480
|
const canCreate = await resolved.guard(ctx, "create");
|
|
481
481
|
const canUpdate = await resolved.guard(ctx, "update");
|
|
482
|
-
if (!canCreate || !canUpdate) throw new
|
|
482
|
+
if (!canCreate || !canUpdate) throw new TRPCError({
|
|
483
|
+
code: "FORBIDDEN",
|
|
484
|
+
message: "Forbidden: upsert requires both create and update permissions"
|
|
485
|
+
});
|
|
483
486
|
return;
|
|
484
487
|
}
|
|
485
|
-
if (!await resolved.guard(ctx, operation)) throw new
|
|
488
|
+
if (!await resolved.guard(ctx, operation)) throw new TRPCError({
|
|
489
|
+
code: "FORBIDDEN",
|
|
490
|
+
message: `Forbidden: ${operation} not allowed`
|
|
491
|
+
});
|
|
486
492
|
}
|
|
487
493
|
};
|
|
488
494
|
const withAuthorize = async (ctx, resource, operation) => {
|
|
489
495
|
if (!resource) return;
|
|
490
|
-
if (!await resolved.checkAuthorize(ctx, resource, operation)) throw new
|
|
496
|
+
if (!await resolved.checkAuthorize(ctx, resource, operation)) throw new TRPCError({
|
|
497
|
+
code: "FORBIDDEN",
|
|
498
|
+
message: `Forbidden: Cannot ${operation} this resource`
|
|
499
|
+
});
|
|
491
500
|
};
|
|
492
501
|
const procedures = {
|
|
493
502
|
list: resolved.procedureFactory("list").input(listInputSchema).query(async ({ ctx, input }) => {
|
|
@@ -570,7 +579,10 @@ function createCrudRouter(config) {
|
|
|
570
579
|
const where = buildWhere(ctx, "update", eq(getIdColumn(), input.id));
|
|
571
580
|
const [existing] = await ctx.db.select().from(table).where(where);
|
|
572
581
|
await withAuthorize(ctx, existing, "update");
|
|
573
|
-
if (!existing) throw new
|
|
582
|
+
if (!existing) throw new TRPCError({
|
|
583
|
+
code: "NOT_FOUND",
|
|
584
|
+
message: "Resource not found or access denied"
|
|
585
|
+
});
|
|
574
586
|
const doUpdate = async (updateData) => {
|
|
575
587
|
const injectData = resolved.getInject(ctx, "update");
|
|
576
588
|
const data = {
|
|
@@ -594,7 +606,10 @@ function createCrudRouter(config) {
|
|
|
594
606
|
const where = buildWhere(ctx, "delete", eq(getIdColumn(), input));
|
|
595
607
|
const [existing] = await ctx.db.select().from(table).where(where);
|
|
596
608
|
await withAuthorize(ctx, existing, "delete");
|
|
597
|
-
if (!existing) throw new
|
|
609
|
+
if (!existing) throw new TRPCError({
|
|
610
|
+
code: "NOT_FOUND",
|
|
611
|
+
message: "Resource not found or access denied"
|
|
612
|
+
});
|
|
598
613
|
const doDelete = async () => {
|
|
599
614
|
let deleted;
|
|
600
615
|
if (softDelete) [deleted] = await ctx.db.update(table).set({ [softDelete.column]: softDelete.getValue() }).where(where).returning();
|
|
@@ -613,8 +628,8 @@ function createCrudRouter(config) {
|
|
|
613
628
|
await withGuard(ctx, "deleteMany");
|
|
614
629
|
const where = buildWhere(ctx, "deleteMany", inArray(getIdColumn(), input));
|
|
615
630
|
const doDeleteMany = async () => {
|
|
616
|
-
if (softDelete) return { deleted: (await ctx.db.update(table).set({ [softDelete.column]: softDelete.getValue() }).where(where).returning()).length };
|
|
617
|
-
else return { deleted: (await ctx.db.delete(table).where(where).returning()).length };
|
|
631
|
+
if (softDelete) return { deleted: (await ctx.db.update(table).set({ [softDelete.column]: softDelete.getValue() }).where(where).returning({ id: getIdColumn() })).length };
|
|
632
|
+
else return { deleted: (await ctx.db.delete(table).where(where).returning({ id: getIdColumn() })).length };
|
|
618
633
|
};
|
|
619
634
|
if (m.deleteMany) return m.deleteMany({
|
|
620
635
|
ctx,
|
|
@@ -635,7 +650,7 @@ function createCrudRouter(config) {
|
|
|
635
650
|
...updateData,
|
|
636
651
|
...injectData
|
|
637
652
|
};
|
|
638
|
-
return { updated: (await ctx.db.update(table).set(data).where(where).returning()).length };
|
|
653
|
+
return { updated: (await ctx.db.update(table).set(data).where(where).returning({ id: getIdColumn() })).length };
|
|
639
654
|
};
|
|
640
655
|
if (m.updateMany) return m.updateMany({
|
|
641
656
|
ctx,
|
|
@@ -648,21 +663,27 @@ function createCrudRouter(config) {
|
|
|
648
663
|
upsert: resolved.procedureFactory("upsert").input(insertSchema).mutation(async ({ ctx, input }) => {
|
|
649
664
|
await withGuard(ctx, "upsert");
|
|
650
665
|
const doUpsert = async (inputData) => {
|
|
651
|
-
const injectData = resolved.getInject(ctx, "create");
|
|
652
|
-
const data = {
|
|
653
|
-
...inputData,
|
|
654
|
-
...injectData
|
|
655
|
-
};
|
|
656
666
|
const inputId = inputData[idField];
|
|
657
667
|
let isNew = true;
|
|
668
|
+
let existing = null;
|
|
658
669
|
if (inputId) {
|
|
659
670
|
const where = buildWhere(ctx, "get", eq(getIdColumn(), inputId));
|
|
660
|
-
const [
|
|
671
|
+
const [found] = await ctx.db.select().from(table).where(where);
|
|
672
|
+
existing = found;
|
|
661
673
|
isNew = !existing;
|
|
674
|
+
if (!isNew && existing) await withAuthorize(ctx, existing, "update");
|
|
662
675
|
}
|
|
676
|
+
const injectData = resolved.getInject(ctx, isNew ? "create" : "update");
|
|
677
|
+
const data = {
|
|
678
|
+
...inputData,
|
|
679
|
+
...injectData
|
|
680
|
+
};
|
|
663
681
|
const [result] = await ctx.db.insert(table).values(data).onConflictDoUpdate({
|
|
664
682
|
target: getIdColumn(),
|
|
665
|
-
set:
|
|
683
|
+
set: {
|
|
684
|
+
...inputData,
|
|
685
|
+
...resolved.getInject(ctx, "update")
|
|
686
|
+
}
|
|
666
687
|
}).returning();
|
|
667
688
|
return {
|
|
668
689
|
data: result,
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@wordrhyme/auto-crud-server",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "0.6.
|
|
4
|
+
"version": "0.6.2",
|
|
5
5
|
"description": "tRPC server utilities for auto-crud - automatic CRUD routers for Drizzle ORM",
|
|
6
6
|
"author": "wordrhyme",
|
|
7
7
|
"license": "MIT",
|
|
@@ -45,12 +45,13 @@
|
|
|
45
45
|
"superjson": "^2.2.6",
|
|
46
46
|
"tsdown": "^0.15.12",
|
|
47
47
|
"typescript": "^5.9.3",
|
|
48
|
+
"vitest": "^4.0.18",
|
|
48
49
|
"zod": "^4.1.13",
|
|
49
|
-
"@internal/eslint-config": "0.3.0",
|
|
50
50
|
"@internal/prettier-config": "0.0.1",
|
|
51
51
|
"@internal/tsconfig": "0.1.0",
|
|
52
|
+
"@internal/tsdown-config": "0.1.0",
|
|
52
53
|
"@internal/vitest-config": "0.1.0",
|
|
53
|
-
"@internal/
|
|
54
|
+
"@internal/eslint-config": "0.3.0"
|
|
54
55
|
},
|
|
55
56
|
"prettier": "@internal/prettier-config",
|
|
56
57
|
"scripts": {
|
|
@@ -60,6 +61,7 @@
|
|
|
60
61
|
"build:watch": "tsdown --watch",
|
|
61
62
|
"test": "vitest --run --passWithNoTests",
|
|
62
63
|
"test:watch": "vitest --watch",
|
|
64
|
+
"test:coverage": "vitest --run --coverage",
|
|
63
65
|
"typecheck": "tsc --noEmit",
|
|
64
66
|
"lint": "eslint",
|
|
65
67
|
"format": "prettier --check . --ignore-path ../../.gitignore"
|