Skip to content

Instantly share code, notes, and snippets.

@rattrayalex
Last active April 14, 2021 14:19
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save rattrayalex/c59d6e2ea6567cadf51c0532b5f15a79 to your computer and use it in GitHub Desktop.
Save rattrayalex/c59d6e2ea6567cadf51c0532b5f15a79 to your computer and use it in GitHub Desktop.
PostGraphile Framework Sketches

I want something that I can share with a development team that's just focused on features and doesn't want to think about the tools at hand very much (but can deeply fine-tune where needed).

Postgraphile seems like an amazing place to start for this.

Here's what I might want to build on top of it (though ideally it'd be baked-in):

Each "table" (think pg_class) should have a single file where ~all logic around that table is contained in as declarative a format as possible.

Smart tags should live in the same, easy-to-see and easy-to-edit place.

Wrapping resolvers and adding new ones should be easy and encouraged, as should other common table-centric operations.

I also think it'd be nice to expose sql from the resolveInfo param since you have to pull off graphile anyway. Directly exposing a queryBuilder to allow run() to do most of the magic of selectGraphQLResultFromTable (this would require some setup work in graphile.queryBuilder().

A sql declaration section should allow the framework to check at boot time that the running database has a matching schema, and to suggest migrations to run if not. CI should fail if running the migrations does not result in the schema specified in the collective sqlDefinitions.

A similar construct to this table-oriented approach would also be great for procedures, triggers, functions, etc – especially if it can encourage colocation of sql generated columns and gql extended resolvers (ideally one would be able to easily and transparently move between one and the other). I haven't thought this part through very much.

export const users = table('users', {
name: 'persons',
sqlDefinitions: sql`
create table app_public.users (
id serial primary key
email citext unique
password blah blah
social_security_number blah blah
);
`,
gqlExtensions: gql`
extend type User: {
fullName: String
}
extend type Query {
randomUser(): User
userBySubQuery(): User
}
`,
omit: 'delete',
attribute: {
password: {
omit: 'all',
},
created_at: {
omit: 'create,update,delete',
},
updated_at: {
omit: 'create,update,delete',
},
},
resolvers: {
Query: {
async randomUser(query, args, context, { graphile, sql }) {
return await graphile.queryBuilder()
.orderBy(sql`random()`)
.limit(1)
.run()
},
async userBySubQuery(query, args, context, { graphile, sql }) {
const subquery = sql`(select * from some_other_table)`
const alias = sql.alias(subquery);
return await graphile.queryBuilder()
.from(subquery, alias)
.where(
sql`${alias}.some_col = bar`,
sql`updated_at <= now() - interval '1 year'`
)
.run()
},
},
User: {
async fullName(user) {
return `${user.first} ${user.last}`;
},
async email(resolver, source, args, context, resolveInfo) {
const result = await resolver();
return result.toLowerCase();
},
socialSecurityNumber: {
requires: {
siblingColumns: [{ column: "id", alias: "$user_id" }],
},
resolve(resolver, user, args, context, resolveInfo) {
if (context.jwtClaims.user_id !== user.$user_id) return null;
return resolver();
},
},
},
}
})
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment