This is a pretty simple question.
How to implement subscriptions in graphql?
I'm asking specifically for when using graphql.js constructors like below ?
I could not find a clean/simple implementation.
There is another question here, but it deals with relay.js - i don't want to unnecessarily increase the nr of external dependencies in my app.
What i have:
module.exports = function (database){
return new GraphQLSchema(
{ query: RootQuery(database)
, mutation: RootMutation(database)
, subscription: RootSubscription(database) -- i can see this in graphiql - see below
}
);
}
function RootSubscription(database){
return new GraphQLObjectType(
{ name: "RootSubscriptionType"
, fields:
{ getCounterEvery2Seconds:
{ type: new GraphQLNonNull(GraphQLInt)
, args :
{ id: { type: GraphQLString }
}
, subscribe(parent, args, context){
// this subscribe function is never called .. why?
const iterator = simpleIterator()
return iterator
}
}
}
}
)
}
I learned that i need a subscribe() which must return an iterator from this github issue.
And here is a simple async iterator. All this iterator does - is to increase and return the counter every 2 seconds. When it reaches 10 it stops.
function simpleIterator(){
return {
[ Symbol.asyncIterator ]: () => {
let i = 0
return {
next: async function(){
i++
await delay(2000)
if(i > 10){
return { done: true }
}
return {
value: i,
done: false
}
}
}
}
}
}
When i run the graphiql subscription, it returns null for some reason:
I'm piecing together code from multiple sources - wasting time and hacking it basically. Can you help me figure this one out?
Subscriptions are such a big feature, where are they properly documented? Where is that snippet of code which you just copy paste - like queries are for example - look here.
Also, i can't use an example where the schema is separate - as a string/from a file. I already created my schema as javascript constructors. Now since im trying to add subscriptions i can't just move back to using a schema as a string. Requires rewriting the entire project. Or can i actually have both? Thanks :)
Related
I'm having the dexie stores showed in the print screen below:
Dexie stores print screen
My goal is to update a dexie field row from a store without losing the rest of the data.
For example: when I edit and save the field "com_name" from the second row (key={2}) I want to update "com_name" only and not lose the rest of the properties, see first and the third row.
I already tried with collection.modify and table.update but both deleted the rest of the properties when used the code below:
dexieDB.table('company').where('dexieKey').equals('{1}')
//USING table.update
//.update(dexieRecord.dexiekey, {
// company: {
// com_name: "TOP SERVE 2"
// }
//})
.modify(
{
company:
{
com_name: TOP SERVE 2
}
}
)
.then(function (updated) {
if (updated)
console.log("Success.");
else
console.log("Nothing was updated.");
})
.catch(function (err) { console.log(err); });
Any idea how can I accomplish that?
Thanks
Alex
You where right to use Table.update or Collection.modify. They should never delete other properties than the ones specified. Can you paste a jsitor.com or jsfiddle repro of that and someone may help you pinpoint why the code doesn't work as expected.
Now that you are saying I realised that company and contact stores are created dynamically and editedRecords store has the indexes explicitly declared therefore when update company or contact store, since dexie doesn't see the indexes will overwrite. I haven't tested it yet but I suspect this is the behaviour.
See the print screen below:
Dexie stores overview
Basically I have json raw data from db and in the browser I create the stores and stores data based on it, see code below:
function createDexieTables(jsonData) { //jsonData - array, is the json from db
const stores = {};
const editedRecordsTable = 'editedRecords';
jsonData.forEach((jsonPackage) => {
for (table in jsonPackage) {
if (_.find(dexieDB.tables, { 'name': table }) == undefined) {
stores[table] = 'dexieKey';
}
}
});
stores[editedRecordsTable] = 'dexieKey, table';
addDataToDexie(stores, jsonData);
}
function addDataToDexie(stores, jsonData) {
dbv1 = dexieDB.version(1);
if (jsonData.length > 0) {
dbv1.stores(stores);
jsonData.forEach((jsonPackage) => {
for (table in jsonPackage) {
jsonPackage[table].forEach((tableRow) => {
dexieDB.table(table).add(tableRow)
.then(function () {
console.log(tableRow, ' added to dexie db.');
})
.catch(function () {
console.log(tableRow, ' already exists.');
});
});
}
});
}
}
This is the json, which I convert to object and save to dexie in the value column and the key si "dexieKey":
[
{
"company": [
{
"dexieKey": "{1}",
"company": {
"com_pk": 1,
"com_name": "CloudFire",
"com_city": "Round Rock",
"serverLastEdit": [
{
"com_pk": "2021-06-02T11:30:24.774Z"
},
{
"com_name": "2021-06-02T11:30:24.774Z"
},
{
"com_city": "2021-06-02T11:30:24.774Z"
}
],
"userLastEdit": []
}
}
]
}
]
Any idea why indexes were not populated when generating them dynamically?
Given the JSON data, i understand what's going wrong.
Instead of passing the following to update():
{
company:
{
com_name: "TOP SERVE 2"
}
}
You probably meant to pass this:
{
"company.com_name": "TOP SERVE 2"
}
Another hint is to do the add within an rw transaction, or even better if you can use bulkAdd() instead to optimize the performance.
What is the best way to collect some specific property from all the leafs of the GraphQL graph, reducing it to some single array? For example, my service functions can "throw" some arbitrary string warnings which I want to collect and supply to the client besides the main data, e.g. expected output:
type EntityOutput {
entity: Entity
warnings: [String!]
}
Resolver:
#Mutation()
async updateEntity(
#Args('id', ParseUUIDPipe) id: string,
#Args('data') input: UpdateDto
): Promise<EntityOutputDto>
{
return {
entity: await this.service.update(id, input),
warnings: [] // ???
};
}
Service method:
async update(id: string, input: UpdateDto): Promise<Entity> {
const entity = await this.repository.findOneOrFail(id, { relations: ['type'] }); // check existence
if (Object.values(input).some(v => v !== undefined)) {
const updateData: Partial<Entity & UpdateDto> = Object.assign({ id }, input);
if (input.isCurrentEntityOfItsType === true) {
await this.typesService.update(entity.type.id, { currentEntityId: id }); // <-- this also can create its own warnings
} else if (input.isCurrentEntityOfItsType === false) {
await this.typesService.update(entity.type.id, { currentEntityId: null as any });
}
await this.repository.save(updateData);
} else {
console.warn(`No properties to change were been provided`); // <-- this is a warning I want to save
}
return this.findOne(id);
}
I think my question can be splitted into 2:
To collect warnings from the service, i.e., in general case, the function calls stack of arbitrary depth. It actually looks more like a general programming problem than a NestJS thing
But even when one implement the feature from the first paragraph the NestJS will walk along the GraphQL graph by itself and there can be additional logs in nested fields.
The solution in its complete general form probably will be over-complicated but at least can anyone suggest the good design for the case represented by the example code?
I have a couple of thoughts:
Should every function in the service return its warnings alongside its main response (for example, in a tuple) so we can incrementally "fold" the array of warnings while "unfolding" the calls stack?
Maybe it would be better to implement using some decorator by which we will mark our service methods?
Maybe RxJS โ the NestJS beloved one โย can offer us some solution? (I don't know a lot about this library/their philosophy)
Actually the default form of the NestJS output is already looking similar to what I want, it's a JSON with 2 root properties: "errors" and "data". And they can be automatically sent to you simultaneously if the error happened is not so fatal to proceed. Can we somehow overwrite the default response object schema and place warnings there?
The whole question is heavily inspired by this SO discussion but it unfortunately says nothing about the actual possible implementation.
So I've implemented a custom context factory which is executed automatically on every GraphQL request and constructs the object of desired format:
app.module.ts:
export interface AppContext {
warnings: string[];
}
const contextFactory: ContextFunction<any, AppContext> = () => ({
warnings: []
});
Now we can benefit from our newly created interface to add strong typings whenever we reference the context, e.g.:
some.resolver.ts
#Mutation()
async remove(
#Args('id', ParseUUIDPipe) id: string,
#Context() ctx: AppContext
): Promise<FindOneDto>
{
return new FindOneDto(await this.service.remove(id, ctx.warnings));
}
Here the service can add its own warnings to the context.
To collect all of them and return to the API caller I override formatResponse function and append the warnings to the extensions (this is a special GraphQL meta-field serving the developing purposes):
app.module.ts:
const graphqlConfig: GqlModuleOptions = {
context: contextFactory,
formatResponse: (
response: GraphQLResponse | null,
context: GraphQLRequestContext<AppContext>,
): GraphQLResponse =>
{
const warnings = context.context.warnings;
if (warnings.length) {
if (response) {
const extensions = response.extensions || (response.extensions = {});
extensions.warnings = warnings;
} else {
return { extensions: { warnings } };
}
}
return response || {};
},
...
}
Similar approach is used in the official Apollo extension example: https://github.com/apollographql/apollo-server/blob/main/packages/apollo-tracing/src/index.ts.
The only drawback I see now is that injecting the context in resolver's arguments breaks the compliance with auto-generated TypeScript interfaces (I use schema-first approach). In such case, we can switch to per-request-based mode so our resolver/service class instance will be created individually for each and every new request: https://docs.nestjs.com/fundamentals/injection-scopes. Now we can access a context right in the methods without introducing any additional parameters. But this comes with increased latencies and, perhaps, memory-consumption. Another approach will be to create a standalone Nest interceptor.
I need to run multiple graphQL queries within a component and within the gatsby-node.js file. (Because Prismic is limited to 20 entries per answer...๐)
I tried the following, just to see if I could create the graphql loop in the default function:
export default () => {
async function allPosts() {
let data
await graphql(`
query allDitherImages {
prismic {
allProjects(sortBy: meta_firstPublicationDate_DESC) {
totalCount
pageInfo {
startCursor
endCursor
hasNextPage
hasPreviousPage
}
edges {
node {
cover_image
cover_imageSharp {
name
}
}
}
}
}
}
`).then(initialRes => {
data = initialRes
})
return data
}
allPosts().then(result => {
console.log(result)
})
return null
}
But then Gatsby tells me that Gatsby related 'graphql' calls are supposed to only be evaluated at compile time, and then compiled away. Unfortunately, something went wrong and the query was left in the compiled code.
How can I run multiple graphql queries?
Thank you in advance :)
Michael
The gatsby-source-prismic-graphql package will create pages for all of your Prismic items (more than just the first 20), as it iterates over all items under the hood, so I'd advise looking into using that if you are looking to generate pages for all of those items.
But if you need to get all items and pass them in the pageContext or something, you'll need to do the recursion yourself in the gatsby-node.
In the gatsby-node, after you have defined the query, you can use something like this to iterate over the results and push to an array.
let documents = [];
async function getAllDocumentsRecursively (query, prop, endCursor = '') {
const results = await graphql(query, { after: endCursor })
const hasNextPage = results.data.prismic[prop].pageInfo.hasNextPage
endCursor = results.data.prismic[prop].pageInfo.endCursor
results.data.prismic[prop].edges.forEach(({node}) => {
documents.push(node)
});
if (hasNextPage) {
await getAllDocumentsRecursively(query, 'allDitherImages ', endCursor)
}
}
await getAllDocumentsRecursively(documentsQuery, 'allDitherImages ');
Then in your createPage, pass the array into the context:
createPage({
path: `/`+ node._meta.uid,
component: allDitherTempate,
context: {
documents: documents
}
})
Excuse the vague code, I can't really copy/paste. :)
I have type in GraphQL like this:
type Thing {
toBe: Boolean
orNot: Boolean
}
I'm trying to create a new property on this type that is an... aggregate of those two. Basically return a new value based upon those values. The code would be like:
if (this.toBe && !this.orNot) { return "To be!"; }
if (!this.toBe && !this.orNot) { return "OrNot!"; }
Does this make sense? So it would return something like:
Thing1 {
toBe: true;
orNot: false;
newProp: "To be!"
}
Yes, you can easily create aggregated fields in your graphql Object types by handling your required logic in that aggregated field resolver. While creating object types, you have instance of that object, and therefore, you can easily create aggregated fields which are not present in your domain models using object's data and this is one of the beauty of graphql. Note that this can differ on each implementation of GraphQL libraries. Following is the example for such use case in JavaScript and Scala.
Example in Graphql.js:
var FooType = new GraphQLObjectType({
name: 'Foo',
fields: {
toBe: { type: GraphQLBoolean},
orNot: { type: GraphQLBoolean},
newProp: { type: GraphQLString,
resolve(obj) {
if (obj.toBe && !obj.orNot) { return "To be!"; }
else { return "OrNot!"; }
}
}
});
Example in Sangria-graphql:
ObjectType(
"Foo",
"graphql object type for foo",
fields[Unit, Foo](
Field("toBe",BooleanType,resolve = _.value.name),
Field("orNot",BooleanType,resolve = _.value.path),
Field("newProp",StringType,resolve = c => {
if (c.value.toBe && !c.value.orNot) "To be!" else "OrNot!"
})
)
)
The various GraphQL server library implementations all have ways to provide resolver functions that can provide the value for a field. You'd have to include it in your schema and write the code for it, but this is a reasonable thing to do and the code you quote is a good starting point.
In Apollo in particular, you pass a map of resolvers that get passed as a resolvers: option to the ApolloServer constructor. If a field doesn't have a resolver it will default to returning the relevant field from the native JavaScript object. So you can write
const resolvers = {
Thing: {
newProp: (parent) => {
if (parent.toBe && !parent.orNot) { return "To be!"; }
if (!parent.toBe && !parent.orNot) { return "OrNot!"; }
return "That is the question";
}
}
};
I'm working on an app with Meteor and React.
I'm trying to have the content of inventories_array from an external MongoDB database, but it's extremely slow. I wait 7 seconds, I have one object, I wait 5 seconds, two other objects, etc...
Spaces = new Mongo.Collection("Space");
Properties = new Mongo.Collection("Property");
Inventories = new Mongo.Collection("Inventory");
if (Meteor.isClient) {
Meteor.subscribe("Property");
Meteor.subscribe("Space");
Meteor.subscribe("Inventory");
Tracker.autorun(function() {
inventories_array = Inventories.find({quantityBooked: 2},{fields: {priceTaxExcl: 1, property: 1, space: 1}}).fetch();
console.log(inventories_array);
}
if (Meteor.isServer) {
Meteor.publish("Property", function () {
return Properties.find();
});
Meteor.publish("Space", function () {
return Spaces.find();
});
Meteor.publish("Inventory", function () {
return Inventories.find();
});
}
The Inventory Object:
{
...
"property" : ObjectId("..."),
"space" : ObjectId("..."),
"quantityBooked":2,
"priceTaxExcl":...,
...
}
I launch the app with MONGO_URL=mongodb://127.0.0.1:27017/mydb meteor run
Any ideas why it's so slow?
If you look at your network tab in the inspector you'll see all the data flowing from the server to the client for each subscription and you'll be able to judge both how large it is and how long it takes.
I'd recommend at a first step that you alter your Inventory publication as follows:
Meteor.publish("Inventory", function () {
if ( this.userId ){ // only return data if we have a logged-in user
return Inventories.find({quantityBooked: 2},{fields: {priceTaxExcl: 1, property: 1, space: 1}});
} else {
this.ready();
}
});
This way your server is only sending the required fields from the required documents (assuming you only want docs where {quantityBooked: 2}.