Skip to content

Commit 973654a

Browse files
committed
✨ feat: default arg implementation
1 parent 2b43cd4 commit 973654a

File tree

8 files changed

+248
-37
lines changed

8 files changed

+248
-37
lines changed

README.md

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,48 @@ const UserRef = implementDefaultObject({
9494
});
9595
```
9696

97+
#### Automatic arg implementation
98+
rumble also supports automatically implementing basic filtering args. Those currently only allow for equality filtering for each field. E.g. the user can pass an email or an id and retrieve only results matching these for equality. Implementation works like this
99+
```ts
100+
const {
101+
// the input arg type, here we rename it to UserWhere
102+
inputType: UserWhere,
103+
// since drizzle wants proper instantiated filter clauses with `eq` calls and references to each field we need a transformer function which converts the object received from gql to a drizzle filter
104+
transformArgumentToQueryCondition: transformUserWhere,
105+
} = implementWhereArg({
106+
// for which table to implement this
107+
tableName: "users",
108+
});
109+
```
110+
usage of the above argument type may look like this
111+
```ts
112+
schemaBuilder.queryFields((t) => {
113+
return {
114+
findManyUsers: t.drizzleField({
115+
type: [UserRef],
116+
args: {
117+
// here we set our default type as type for the where argument
118+
where: t.arg({ type: UserWhere }),
119+
},
120+
resolve: (query, root, args, ctx, info) => {
121+
return db.query.users.findMany(
122+
query(
123+
ctx.abilities.users.filter("read",
124+
// this additional object offers temporarily injecting additional filters to our existing ability filters
125+
{
126+
// the inject field allows for temp, this time only filters to be added to our ability filters. They will only be applied for this specific call.
127+
inject: {
128+
// where conditions which are injected will be applied with an AND rather than an OR so the injected filter will further restrict the existing restrictions rather than expanding them
129+
where: transformUserWhere(args.where) },
130+
})
131+
)
132+
);
133+
},
134+
}),
135+
};
136+
});
137+
```
138+
97139
### Defining queries and mutations
98140
Now we can define some things you can do. Again we use pothos for that. So please refer to [the docs](https://pothos-graphql.dev/docs/plugins/drizzle) if something is unclear.
99141
```ts

bun.lockb

0 Bytes
Binary file not shown.

example/src/main.ts

Lines changed: 32 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,13 @@ export const db = drizzle(
1313
{ schema },
1414
);
1515

16-
const { abilityBuilder, schemaBuilder, yoga, implementDefaultObject } = rumble({
16+
const {
17+
abilityBuilder,
18+
schemaBuilder,
19+
yoga,
20+
implementDefaultObject,
21+
implementWhereArg,
22+
} = rumble({
1723
db,
1824
context(request) {
1925
return {
@@ -65,15 +71,28 @@ const PostRef = schemaBuilder.drizzleObject("posts", {
6571
// DEFINE ROOT QUERIES AND MUTATOINS
6672
//
6773

74+
const {
75+
inputType: UserWhere,
76+
transformArgumentToQueryCondition: transformUserWhere,
77+
} = implementWhereArg({
78+
tableName: "users",
79+
});
80+
6881
schemaBuilder.queryFields((t) => {
6982
return {
7083
findManyUsers: t.drizzleField({
7184
type: [UserRef],
85+
args: {
86+
where: t.arg({ type: UserWhere }),
87+
},
7288
resolve: (query, root, args, ctx, info) => {
73-
return db.query.users.findMany({
74-
...query,
75-
...ctx.abilities.users.filter("read"),
76-
});
89+
return db.query.users.findMany(
90+
query(
91+
ctx.abilities.users.filter("read", {
92+
inject: { where: transformUserWhere(args.where) },
93+
}),
94+
),
95+
);
7796
},
7897
}),
7998
};
@@ -84,10 +103,9 @@ schemaBuilder.queryFields((t) => {
84103
findManyPosts: t.drizzleField({
85104
type: [PostRef],
86105
resolve: (query, root, args, ctx, info) => {
87-
return db.query.posts.findMany({
88-
...query,
89-
...ctx.abilities.posts.filter("read"),
90-
});
106+
return db.query.posts.findMany(
107+
query(ctx.abilities.posts.filter("read")),
108+
);
91109
},
92110
}),
93111
};
@@ -100,10 +118,11 @@ schemaBuilder.queryFields((t) => {
100118
resolve: (query, root, args, ctx, info) => {
101119
return (
102120
db.query.users
103-
.findFirst({
104-
...query,
105-
where: ctx.abilities.users.filter("read").where,
106-
})
121+
.findFirst(
122+
query({
123+
where: ctx.abilities.users.filter("read").where,
124+
}),
125+
)
107126
// note that we need to manually raise an error if the value is not found
108127
.then(assertFindFirstExists)
109128
);

lib/abilities/builder.ts

Lines changed: 31 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
1-
import { or } from "drizzle-orm";
1+
import { log } from "node:console";
2+
import { and, or } from "drizzle-orm";
23
import type {
34
GenericDrizzleDbTypeConstraints,
45
QueryConditionObject,
@@ -110,7 +111,16 @@ export const createAbilityBuilder = <
110111
} = {} as any;
111112

112113
const createEntityObject = (entityKey: DBQueryKey) => ({
113-
filter: (action: Action) => {
114+
filter: (
115+
action: Action,
116+
options?: {
117+
/**
118+
* Additional conditions applied only for this call. Useful for injecting one time additional filters
119+
* for e.g. user args in a handler.
120+
*/
121+
inject?: QueryConditionObject;
122+
},
123+
) => {
114124
const conditionsPerEntity = registeredConditions[entityKey];
115125
if (!conditionsPerEntity) {
116126
throw "TODO (No allowed entry found for this condition) #1";
@@ -152,9 +162,16 @@ export const createAbilityBuilder = <
152162
}
153163
}
154164

165+
if (options?.inject?.limit && highestLimit < options.inject.limit) {
166+
highestLimit = options.inject.limit;
167+
}
168+
155169
let combinedAllowedColumns: Record<string, any> | undefined =
156170
undefined;
157-
for (const conditionObject of allConditionObjects) {
171+
for (const conditionObject of [
172+
...allConditionObjects,
173+
options?.inject ?? {},
174+
]) {
158175
if (conditionObject.columns) {
159176
if (combinedAllowedColumns === undefined) {
160177
combinedAllowedColumns = conditionObject.columns;
@@ -171,16 +188,25 @@ export const createAbilityBuilder = <
171188
.filter((o) => o.where)
172189
.map((o) => o.where);
173190

174-
const combinedWhere =
191+
let combinedWhere =
175192
accumulatedWhereConditions.length > 0
176193
? or(...accumulatedWhereConditions)
177194
: undefined;
178195

179-
return {
196+
if (options?.inject?.where) {
197+
combinedWhere = combinedWhere
198+
? and(combinedWhere, options.inject.where)
199+
: options.inject.where;
200+
}
201+
202+
const ret = {
180203
where: combinedWhere,
181204
columns: combinedAllowedColumns,
182205
limit: highestLimit,
183206
};
207+
208+
//TODO make this typesafe per actual entity
209+
return ret;
184210
},
185211
});
186212

0 commit comments

Comments
 (0)