11import { createServer } from "node:http" ;
2- import { and } from "drizzle-orm" ;
32import { drizzle } from "drizzle-orm/node-postgres" ;
43import {
54 assertFindFirstExists ,
@@ -24,6 +23,8 @@ import * as schema from "./db/schema";
2423export 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
139140const 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
166171const 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:
268280object ( {
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
292299const { 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