-
-
Notifications
You must be signed in to change notification settings - Fork 127
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Benjie's responses megathread #153
Comments
Source: graphile/graphile-engine#471 (comment)
Correct.
We start with an inflectors object that's something like Hooks get called on the result of each other to calculate the final value, so you can think of it as: const initialInflectors = {pluralize, singularize, camelCase}; // Supplied by graphile-build
const finalInflectors =
hook3( // Your Hook Here
hook2( // from @graphile-contrib/pg-simplify-inflector
hook1( // from graphile-build-pg
initialInflectors
)
)
); In this situation you might have that
You shouldn't need to even worry about any of this, the const { makeAddInflectorsPlugin } = require("graphile-utils");
module.exports = makeAddInflectorsPlugin({
allRows(table: PgClass) {
return this.camelCase(
`all-${this.distinctPluralize(this._singularizedTableName(table))}`
);
},
}, true); |
How lookahead works/could work w.r.t. interfaces/unions: graphile/crystal#387 (comment) |
graphile/crystal#387 (comment) on how the lookahead system works (and might be modified to support unions/interfaces). |
We don't have any examples, but it's definitely possible. You can implement it with const resolvers = {
TypeName: {
fieldName: {
subscribe(parent, args, context, info) {
return makeYourAsyncIteratorHere();
},
resolve(asyncIteratorValue, args, context, info) {
return someFunctionOf(asyncIteratorValue);
}
}
}
}; Your async iterator can return a stream of events from anywhere. https://www.graphile.org/postgraphile/make-extend-schema-plugin/ https://discordapp.com/channels/489127045289476126/498852330754801666/673926777512656916 |
Way to get the inflectors so you can write external code that depends on them without building the entire GraphQL schema again. const { getPostGraphileBuilder } = require('postgraphile-core');
export async function getBuild(connectionString, schema, options) {
const pool = new Pool({connectionString});
try {
const builder = await getPostGraphileBuilder(pool, schema, options);
return builder.createBuild();
} finally {
pool.end();
}
} The above function gives you a way to get the const build = await getBuild(connectionString, schema, options); You can use inflectors from const buildPromise = getBuild(connectionString, schema, options);
async function getGraphQLColumnName(schemaName, tableName, columnName) {
const build = await buildPromise;
const table = build.pgIntrospectionResultsByKind.class.find(rel => rel.name === tableName && rel.namespaceName === schemaName);
const column = build.pgIntrospectionResultsByKind.attribute.find(attr => attr.name === columnName && attr.classId === table.id);
return build.inflection.column(attr);
} https://discordapp.com/channels/489127045289476126/498852330754801666/675281797080416289 |
Jest test to ensure RLS is enabled on all tables (from 2017). import { withRootDb } from '../test_helpers';
test('RLS is enabled for all tables in mindtree_public', () => withRootDb(async (client) => {
const { rows } = await client.query(`
WITH nsp AS (
SELECT oid FROM pg_namespace WHERE nspname = 'mindtree_public'
)
SELECT relname AS "table"
FROM pg_class
INNER JOIN nsp
ON (nsp.oid = pg_class.relnamespace)
WHERE relkind = 'r'
AND relrowsecurity IS NOT DISTINCT FROM FALSE
`);
expect(rows).toHaveLength(0);
})); |
https://discordapp.com/channels/489127045289476126/498852330754801666/677462518419161089 |
You can indeed issue GraphQL requests from various contexts, including within a resolver. To do so you need the following:
Issuing a GraphQL operation from inside a resolver example: /*
* Assuming you have access to a `build` object, e.g. inside a
* `makeExtendSchemaPlugin`, you can extract the `graphql` function
* from the `graphql` library here like so:
*/
const { graphql: { graphql } } = build;
/*
* Failing the above: `import { graphql } from 'graphql';` but beware of
* duplicate `graphql` modules in your `node_modules` causing issues.
*/
function myResolver(parent, args, context, info) {
// Whatever GraphQL query you wish to issue:
const document = /* GraphQL */ `
query MyQuery($userId: Int!) {
userById(id: $userId) {
username
}
}
`;
// The name of the operation in your query document (optional)
const operationName = 'MyQuery';
// The variables for the query
const variables = { userId: args.userId };
const { data, errors } = await graphql(
info.schema,
document,
null,
context,
variables,
operationName
);
return data.userById.username;
} |
via Benjie's responses megathread graphile#153 (comment)
via Benjie's responses megathread graphile#153 (comment)
Implicitly setting the relevant const myPlugin = builder => {
builder.hook("GraphQLObjectType:fields:field", (field, _, { Self, scope }) => {
if (!field.args || !field.args.companyId || !scope.isRootQuery) {
return field;
}
// Remove the companyId field and default it from context
const { companyId, ...remainingArgs } = field.args;
const { resolve: oldResolve } = field;
return {
...field,
args: remainingArgs,
resolve(parent, args, context, info) {
return oldResolve(parent, {
...args,
// Grab the relevant companyId from the JWT, or some other value
// from additionalGraphQLContextFromRequest if you prefer
companyId: context.jwtClaims.companyId,
}, context, info);
}
};
});
}; |
Wraps all resolvers returning a DateTime and automatically calls const MyPlugin = makeWrapResolversPlugin(
(context, build, field, options) =>
build.graphql.getNamedType(field.type).name === "Datetime"
? field
: null,
(field) => async (resolver) => {
const d = await resolver();
// TODO: handle case where `field` is a list.
return d instanceof Date ? d.toISOString() : d;
}
); |
For watch mode in production you might not want to install our whole watch schema with its event triggers and what not; instead you can use the “manual” watch trigger. Then just fire that NOTIFY after all your migrations have completed. For this manual watch mode you still use postgraphile(DATABASE_URL, SCHEMA, {
watchPg: true,
graphileBuildOptions: {
pgSkipInstallingWatchFixtures: true,
}
}) Then when you're ready for PostGraphile to reload your schema, you send a NOTIFY "postgraphile_watch", json_build_object('type', 'manual')::text;
|
IMPORTANT: Wrapping an async iterator: // THIS IS NOT SAFE, DO NOT USE
async function* doSomethingWith(asyncIterator, context) {
console.log("Subscription started");
try {
for await (const val of asyncIterator) {
yield val
}
} finally {
console.log("Subscription ended");
}
} -- graphile/crystal#1363 (comment) e.g. as might be done when wrapping a module.exports = (builder) => {
builder.hook('GraphQLObjectType:fields:field', (field, build, context) => {
if (!context.scope.isRootSubscription) return field;
const oldSubscribe = field.subscribe;
return {
...field,
async subscribe(
rootValue,
args,
context /* DIFFERENT TO THE CONTEXT ABOVE */,
info
) {
const asyncIterator = await oldSubscribe(rootValue, args, context, info);
const derivativeAsyncIterator = doSomethingWith(asyncIterator, context);
return derivativeAsyncIterator;
}
}
}, ['MySubscribeWrapperThingy'], [], ['PgSubscriptionResolver']);
}; |
|
Here's an example of a plugin that adds a condition to a PostGraphile collection by default: https://github.com/graphile-contrib/pg-omit-archived/blob/6ce933efc9e83bbf9415d3ab1393326111f84b42/index.js#L194-L255 The important part is the Stripped down it'd be something like: const MyFilterPlugin = builder => {
builder.hook(
"GraphQLObjectType:fields:field:args",
(args, build, context) => {
const { pgSql: sql } = build;
const { addArgDataGenerator } = context;
if (...it's not relevant to us...) {
return args;
}
addArgDataGenerator(fieldArgs => ({
pgQuery(queryBuilder) {
queryBuilder.where(sql.fragment`my_condition = here`);
},
}));
return args;
},
);
}; The main parts you'd need to implement are a) finding out which fields are relevant to you (e.g. if If you just want to apply it to a specific field If not relevant; |
A simple server (microservice) to enqueue jobs to Graphile Worker; const { makeWorkerUtils } = require("graphile-worker");
const express = require("express");
const bodyParser = require("body-parser");
const app = express();
app.use(bodyParser.json());
async function main() {
const workerUtils = await makeWorkerUtils({
connectionString: "postgres:///my_db",
});
try {
await workerUtils.migrate();
app.post('/add_job', async (req, res, next) => {
try {
assertRequestIsAuthenticated(req);
await workerUtils.addJob(req.body.task, req.body.payload, /* etc */);
} catch (e) {
next(e);
}
});
app.listen(3030);
} finally {
await workerUtils.release();
}
}
main().catch((err) => {
console.error(err);
process.exit(1);
}); |
I often post useful answers in various locations (chat, issues, PRs, StackOverflow, etc). It'd be great if these answers were rewritten and inserted into the documentation for easy reference. Some might make sense in an FAQ format, some might make sense on the relevant pages of the documentation.
This thread collects some of these responses with the intention of later going through them and putting them into the docs - of course if someone wants to take on doing this that would be very welcome!
One response per message, please; each entry should:
- [ ] Added to docs
to indicate completionDiscord, #help-and-support, 20th December 2019 @ 3:17pm UTC
(please include timezone!))Thanks!
The text was updated successfully, but these errors were encountered: