Skip to content

Commit 04023bf

Browse files
committed
📚 docs
1 parent 8e35b34 commit 04023bf

File tree

2 files changed

+63
-40
lines changed

2 files changed

+63
-40
lines changed

‎README.md‎

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,11 +16,12 @@ npm i @m1212e/rumble
1616
then call the rumble creator:
1717
```ts
1818
import * as schema from "./db/schema";
19+
import * as relations from "./db/relations";
1920
import { rumble } from "@m1212e/rumble";
2021

2122
export const db = drizzle(
2223
"postgres://postgres:postgres@localhost:5432/postgres",
23-
{ schema }
24+
{ schema, relations }
2425
);
2526

2627
const { abilityBuilder } = rumble({ db });
@@ -101,7 +102,10 @@ Applying filters on objects is done automatically if you use the helpers. If you
101102
const PostRef = schemaBuilder.drizzleObject("posts", {
102103
name: "Post",
103104
// apply the application level filters
104-
applyFilters: abilityBuilder.registeredFilters.posts.read,
105+
applyFilters: abilityBuilder._.registeredFilters({
106+
action: "read",
107+
table: "posts",
108+
}),
105109
fields: (t) => ({
106110
...
107111
```

‎example/src/main.ts‎

Lines changed: 57 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
import { createServer } from "node:http";
2-
import { and } from "drizzle-orm";
32
import { drizzle } from "drizzle-orm/node-postgres";
43
import {
54
assertFindFirstExists,
@@ -24,6 +23,8 @@ import * as schema from "./db/schema";
2423
export const db = drizzle(
2524
"postgres://postgres:postgres@localhost:5432/postgres",
2625
{
26+
// although drizzle might not force you to pass both schema and relations,
27+
// rumble needs both to be able to infer relations between tables
2728
relations,
2829
schema,
2930
},
@@ -111,7 +112,7 @@ abilityBuilder.posts.allow(["update", "delete"]).when(({ userId }) => {
111112
}; // we can return a standard ability which allows things under a specific condition
112113
}
113114
if (userId === 2) {
114-
return "allow"; // we can return a wildcard, which allows everything
115+
return "allow"; // we can return a wildcard, which allows everything, without any conditions
115116
}
116117
return undefined; // we can return nothing, which does not allow anything
117118
});
@@ -138,7 +139,8 @@ abilityBuilder.posts.filter("read").by(({ context: _context, entities }) => {
138139
// we define the schema of the post object so we can later use it in our queries as a return type
139140
const PostRef = schemaBuilder.drizzleObject("posts", {
140141
name: "Post",
141-
// this is how you can apply application level filter in manual object definitions
142+
// this is how you can apply application level filter (the one defined by abilityBuilder.posts.filter("read").by)
143+
// in manual object definitions
142144
// the helpers will do this for you automatically
143145
applyFilters: abilityBuilder._.registeredFilters({
144146
action: "read",
@@ -150,7 +152,10 @@ const PostRef = schemaBuilder.drizzleObject("posts", {
150152
author: t.relation("author", {
151153
// this is how you can apply the above abilities to the queries
152154
// you define the action you want the filters for by passing it to the filter call
153-
query: (_args, ctx) => ctx.abilities.users.filter("read").query.single,
155+
query: (_args, ctx) =>
156+
// for a findFirst query (1:1 relation)
157+
// we want a query filter
158+
ctx.abilities.users.filter("read").query.single,
154159
}),
155160
}),
156161
});
@@ -159,18 +164,19 @@ const PostRef = schemaBuilder.drizzleObject("posts", {
159164
160165
Since this might get a bit verbose, rumble offers a helper for defining default object implementations.
161166
It will expose all fields and relations but apply the above abilities to the queries so you don't have
162-
to worry about data getting returned which the caller should not be able to fetch.
167+
to worry about data getting returned which the caller should not be able to see
163168
164169
*/
165170

166171
const UserRef = object({
167172
// name of the table you want to implement ("posts" in the above example)
168173
table: "users",
169174
// optionally specify the name of the object ("Post" in the above example)
170-
refName: "User",
175+
// refName: "User",
171176
// optionally, we can extend this with some custom fields (you can also overwrite existing fields in case you need to change default behavior)
172177
adjust(t) {
173178
return {
179+
// the user should have the "somethingElse" field in addition to the default fields
174180
somethingElse: t.field({
175181
type: "String",
176182
args: {
@@ -179,6 +185,10 @@ const UserRef = object({
179185
resolve: (parent, args, _context, _info) =>
180186
`Hello ${parent.name}, you have provided the number: ${args.amount}`,
181187
}),
188+
// here we also could overwrite the "name" field to apply some custom logic
189+
// name: t.exposeString("name", {
190+
// resolve: (parent) => `Mr./Ms. ${parent.name}`
191+
// })
182192
};
183193
},
184194
});
@@ -265,6 +275,8 @@ query({
265275
table: "users",
266276
});
267277

278+
// for the comments table we fully rely on the helpers. This is all you need to do to get a full
279+
// CRUD api for the comments table with permissions and filtering applied:
268280
object({
269281
table: "comments",
270282
});
@@ -274,21 +286,16 @@ query({
274286

275287
/*
276288
277-
Lastly, we want to implement the mutations so we can actually edit some data.
289+
Finally, we want to implement the mutations so we can actually edit some data.
278290
We can use the schemaBuilder to do that.
279-
280-
NOTE: The below example uses the assertFirstEntryExists mapper.
281-
rumble offers two helpers to map drizzle responses to graphql compatible responses:
282-
283-
`assertFirstEntryExists` - throws an error if the response does not contain a single entry
284-
`assertFindFirstExists` - makes the result of a findFirst query compatible with graphql. Also throws if not present.
291+
285292
*/
286293

287294
// OPTIONAL: If you want to use graphql subscriptions, you can use the pubsub helper
288295
// this makes notifying subscribers easier. The rumble helpers all support subscriptions
289296
// right out of the box, so all subscriptions will automatically get notified if necessary
290297
// the only thing you have to do is to call the pubsub helper with the table name
291-
// and embed the helper into your mutations
298+
// and embed the helper into your mutations or other places where data changes
292299
const { updated: updatedUser, created: createdUser } = pubsub({
293300
table: "users",
294301
});
@@ -302,20 +309,17 @@ schemaBuilder.mutationFields((t) => {
302309
newName: t.arg.string({ required: true }),
303310
},
304311
resolve: async (query, _root, args, ctx, _info) => {
305-
// for update mutations, rumble exports the 'mapNullFieldsToUndefined' helper
306-
// which might become handy in some situtations
307-
308312
await db
309313
.update(schema.users)
310314
.set({
311315
name: args.newName,
312316
})
313317
.where(
314-
and(
315-
ctx.abilities.users.filter("update").merge({
316-
where: { id: args.userId },
317-
}).sql.where,
318-
),
318+
// we need an sql condition here sowe use .sql.where instead of .query.x as we did above
319+
ctx.abilities.users.filter("update").merge({
320+
where: { id: args.userId },
321+
}).sql.where,
322+
// the output of this is a normal drizzle sql condition and can be further processed with e.g. `and()` etc.
319323
);
320324

321325
// this notifies all subscribers that the user has been updated
@@ -330,7 +334,9 @@ schemaBuilder.mutationFields((t) => {
330334
}),
331335
),
332336
)
333-
// this maps the db response to a graphql response
337+
// this helper maps the db response to a graphql response
338+
// `assertFirstEntryExists` - throws an error if the response array does not contain a single entry
339+
// `assertFindFirstExists` - makes the result of a findFirst query compatible with graphql. Also throws if not present.
334340
.then(assertFindFirstExists)
335341
);
336342
},
@@ -384,12 +390,12 @@ schemaBuilder.mutationFields((t) => {
384390
};
385391
});
386392

387-
// /*
393+
/*
388394
389-
// Finally, we can start the server. We use graphql-yoga under the hood. It allows for
390-
// a very simple and easy to use GraphQL API and is compatible with many HTTP libraries and frameworks.
395+
Finally, we can start the server. We use graphql-yoga under the hood. It allows for
396+
a very simple and easy to use GraphQL API and is compatible with many HTTP libraries and frameworks.
391397
392-
// */
398+
*/
393399

394400
// when we are done defining the objects, queries and mutations,
395401
// we can start the server
@@ -400,6 +406,15 @@ server.listen(3000, () => {
400406

401407
// if you also need a REST API built from your GraphQL API, you can use 'createSofa()' instead or in addition
402408

409+
/*
410+
411+
That's it for the server/api part! Running the above demo will get you going with a fully functional
412+
GraphQL API with permissions and filtering applied. You can now query users, posts and comments
413+
according to the defined permissions.
414+
We covered most of the relevant features of rumble, but there is more to explore.
415+
416+
*/
417+
403418
// Making calls to the API
404419

405420
// this can run on the dev machine to create a client for
@@ -411,19 +426,23 @@ await clientCreator({
411426
apiUrl: "http://localhost:3000/graphql",
412427
});
413428

429+
// it will write a typesafe client to the specified output path on your filesystem
414430
// which then can be used like this:
415-
// import { client } from "./generated-client/client";
416431

417-
// const r1 = client.liveQuery.users({
418-
// id: true,
419-
// name: true,
420-
// });
432+
/*
433+
import { client } from "./generated-client/client";
434+
435+
const r1 = client.liveQuery.users({
436+
id: true,
437+
name: true,
438+
});
421439
422-
// r1.subscribe((s) => s?.at(0));
440+
r1.subscribe((s) => s?.at(0));
423441
424-
// const a = client.subscribe.users({
425-
// id: true,
426-
// name: true,
427-
// });
442+
const a = client.subscribe.users({
443+
id: true,
444+
name: true,
445+
});
428446
429-
// a.subscribe((s) => s.at(0));
447+
a.subscribe((s) => s.at(0));
448+
*/

0 commit comments

Comments
 (0)