ApolloGraphQL: useSubscription Hook Syntax with onSubscriptionData? - apollo

I'm trying to build an Apollo useSubscription hook that uses onSubscriptionData.
I've looked in the Apollo docs, but I haven't yet an example.
E.g. something like:
const { loading, error, data } = useSubscription(
INCOMING_MESSAGES_SUBSCRIPTION_QUERY,
{
variables: {"localUserId": Meteor.userId()},
onSubscriptionData: myFunctionThatRunsWhenSubscriptionDataArrives
}
);
That can't be right yet, because it doesn't include OnSubscriptionDataOptions<TData>, which is mentioned in the Apollo docs.
What is the correct way to build a useSubscription hook that uses onSubscriptionData?

The onSubscriptionData function is passed a single options parameter of the type OnSubscriptionDataOptions. The options object has two properties:
client -- the ApolloClient instance used to query the server
subscriptionData -- an object with the following properties: loading, data, error
Example usage:
const { loading, error, data } = useSubscription(
INCOMING_MESSAGES_SUBSCRIPTION_QUERY,
{
variables: {"localUserId": Meteor.userId()},
onSubscriptionData: ({ subscriptionData: { data } }) => {
// do something with `data` here
}
},
)

Related

is there a way to get string (data) from text file stored in s3 in Alexa localisation.js file?

Problem:
I am trying to get the data from a text file stored in s3, I get it right in intent handler using a sync await but I want to get string in localisation file as I am trying to implement the solution in 2 languages.
I am getting err saying skill does not respond correctly.
This is file.js
const AWS = require('aws-sdk');
//========================
// This step is not required if you are running your code inside lambda or in
// the local environment that has AWS set up
//========================
const s3 = new AWS.S3();
async function getS3Object (bucket, objectKey) {
try {
const params = {
Bucket: 'my-bucket',
Key: 'file.txt',
};
const data = await s3.getObject(params).promise();
let dat = data.Body.toString('utf-8');
return dat;
} catch (e) {
throw new Error(`Could not retrieve file from S3: ${e.message}`);
}
}
module.exports = getS3Object;
this is the localisation.js file code
const dataText = require('file.js');
async let textTitle = await dataText().then(); **// this does not work**
module.exports = {
en: {
translation: {
WELCOME_BACK_MSG : textTitle,
}
},
it: {
translation: {
WELCOME_MSG: textTitle,
}
}
}
The problem is that in your localisation.js file you are trying to export something that is obtained via an asynchronous function call, but you cannot do that directly, module.exports is assigned and returned synchronously. Please, see for instance this SO question and answer for an in-deep background.
As you are mentioning Alexa skill, and for the name of the file, localisation.js, I assume you are trying something similar to the solution proposed in this GitHub repository.
Analyzing the content of the index.js file they provide, it seems the library is using i18next for localisation.
The library provides the concept of backend if you need to load your localisation information from an external resource.
You can implement a custom backend, although the library offers one that could fit your needs, i18next-http-backend.
As indicated in the documentation, you can configure the library to fetch your localization resources with this backend with something like the following:
import i18next from 'i18next';
import Backend from 'i18next-http-backend';
i18next
.use(Backend)
.init({
backend: {
// for all available options read the backend's repository readme file
loadPath: '/locales/{{lng}}/{{ns}}.json'
}
});
Here in SO you can find a more complete example.
You need to provide a similar configuration to the localisation interceptor provided in the Alexa skill example project, perhaps something like:
import HttpApi from 'i18next-http-backend';
/**
* This request interceptor will bind a translation function 't' to the handlerInput
*/
const LocalizationInterceptor = {
process(handlerInput) {
const localisationClient = i18n
.use(HttpApi)
.init({
lng: Alexa.getLocale(handlerInput.requestEnvelope),
// resources: languageStrings,
backend: {
loadPath: 'https://your-bucket.amazonaws.com/locales/{{lng}}/translations.json',
crossDomain: true,
},
returnObjects: true
});
localisationClient.localise = function localise() {
const args = arguments;
const value = i18n.t(...args);
if (Array.isArray(value)) {
return value[Math.floor(Math.random() * value.length)];
}
return value;
};
handlerInput.t = function translate(...args) {
return localisationClient.localise(...args);
}
}
};
Please, be aware that instead of a text file you need to return a valid son file with the appropriate translations:
{
"WELCOME_MSG" : "Welcome!!",
"WELCOME_BACK_MSG" : "Welcome back!!"
}

Apollo GraphQL client doesn't return cached nested types in a query

I'm performing a query to get PowerMeter details in which contains another type inside called Project. I write the query this way:
query getPowerMeter($powerMeterId: ID!) {
powerMeter: powerMeter(powerMeterId: $powerMeterId) {
id
name
registry
project {
id
name
}
}
}
When I perform the query for the first time, project is successfully returned. The problem is that when I perform subsequent queries with the same parameters and default fetchPolicy (cache-first), project isn't returned anymore.
How may I solve this problem?
Also, I call readFragment to check how powerMeter is saved in the cache and the response shows that powerMeter has project saved.
const frag = client.readFragment({
fragment: gql`
fragment P on PowerMeter {
id
name
registry
project {
id
name
}
}
`,
id: 'PowerMeter:' + powerMeterId,
});
Power Meter returned first time
{
"powerMeter":{
"id":"7168adb4-4198-443e-ab76-db0725be2b18",
"name":"asd123123",
"registry":"as23",
"project":{
"id":"41d8e71b-d1e9-41af-af96-5b4ae9e492c1",
"name":"ProjectName",
"__typename":"Project"
},
"__typename":"PowerMeter"
}
}
Fragment after calling power meter first time
{
"id":"7168adb4-4198-443e-ab76-db0725be2b18",
"name":"asd123123",
"registry":"as23",
"project":{
"id":"41d8e71b-d1e9-41af-af96-5b4ae9e492c1",
"name":"ProjectName",
"__typename":"Project"
},
"__typename":"PowerMeter"
}
Power Meter returned second time
{
"powerMeter":{
"id":"7168adb4-4198-443e-ab76-db0725be2b18",
"name":"asd123123",
"registry":"as23",
"__typename":"PowerMeter"
}
}
Fragment after calling power meter second time
{
"id":"7168adb4-4198-443e-ab76-db0725be2b18",
"name":"asd123123",
"registry":"as23",
"project":{
"id":"41d8e71b-d1e9-41af-af96-5b4ae9e492c1",
"name":"ProjectName",
"__typename":"Project"
},
"__typename":"PowerMeter"
}
Edit 1: Fetching Query
The code below is how I'm fetching data. I'm using useApolloClient and not a query hook because I'm using AWS AppSync and it doesn't support query hook yet.
import { useApolloClient } from '#apollo/react-hooks';
import gql from 'graphql-tag';
import { useEffect, useState } from 'react';
export const getPowerMeterQuery = gql`
query getPowerMeter($powerMeterId: ID!) {
powerMeter: powerMeter(powerMeterId: $powerMeterId) {
id
name
registry
project {
id
name
}
}
}
`;
export const useGetPowerMeter = (powerMeterId?: string) => {
const client = useApolloClient();
const [state, setState] = useState<{
loading: boolean;
powerMeter?: PowerMeter;
error?: string;
}>({
loading: true,
});
useEffect(() => {
if (!powerMeterId) {
return setState({ loading: false });
}
client
.query<GetPowerMeterQueryResponse, GetPowerMeterQueryVariables>({
query: getPowerMeterQuery,
variables: {
powerMeterId,
},
})
.then(({ data, errors }) => {
if (errors) {
setState({ loading: false, error: errors[0].message });
}
console.log(JSON.stringify(data));
const frag = client.readFragment({
fragment: gql`
fragment P on PowerMeter {
id
name
registry
project {
id
name
}
}
`,
id: 'PowerMeter:' + powerMeterId,
});
console.log(JSON.stringify(frag));
setState({
loading: false,
powerMeter: data.powerMeter,
});
})
.catch(err => setState({ loading: false, error: err.message }));
}, [powerMeterId]);
return state;
};
Edit 2: Fetching Policy Details
When I use fetchPolice equals cache-first or network-only, the error persists. When I use no-cache, I don't get the error.
I think this might have been the solution:
https://github.com/apollographql/apollo-client/issues/7050
Probably way too late, but it could help people coming to this issue in the future.
When using apollo client's InMemoryCache it seems you need to provide a list of possible types so the fragment matching can be done correctly when using the InMemoryCache.
You can do that manually when having few union types and a pretty stable API which doesn't change very often.
Or you automatically generate these types into a json file, which you can use directly in the InMemoryCache's possibleTypes config directly.
Visit this link to the official docs to find out how to do it.
Cheers.

Apollo client writeQuery updates stores, but UI componens only updates after the second function call

"apollo-cache-inmemory": "^1.6.2",
"apollo-client": "^2.6.3",
I setup a simple subscription with the client.subscribe method and try to update the store with the client.writeQuery method
export default class App extends Component<Props> {
componentDidMount() {
this.purchaseSubscription = client.subscribe({
query: PURCHASE_ASSIGNED_SUBSCRIPTION,
variables: { status: ['INPREPARATION', 'PROCESSED', 'READYFORCOLLECTION', 'ONTHEWAY', 'ATLOCATION'] },
}).subscribe({
next: (subscriptionData) => {
const { cache } = client;
const prev = cache.readQuery({
query: MY_PURCHASES,
variables: { status: ['INPREPARATION', 'PROCESSED', 'READYFORCOLLECTION', 'ONTHEWAY', 'ATLOCATION'] },
});
const newPurchase = subscriptionData.data.purchaseAssignedToMe;
const data = { myPurchases: [...prev.myPurchases, newPurchase] };
cache.writeQuery({
query: MY_PURCHASES,
variables: { status: ['INPREPARATION', 'PROCESSED', 'READYFORCOLLECTION', 'ONTHEWAY', 'ATLOCATION'] },
data,
});
},
error: (err) => { console.error('err', err) },
});
}
render() {
return (
<ApolloProvider client={client}>
<AppContainer />
</ApolloProvider>
);
}
}
The store gets updated after the call, however the UI component is only re-rendered only on the second publish event.
The UI components is setup the following way:
<Query
query={MY_PURCHASES}
variables={{ status: ['INPREPARATION', 'PROCESSED', 'READYFORCOLLECTION', 'ONTHEWAY', 'ATLOCATION'] }}
>
...
<Query />
By reading the cache after the writeQuery is called I was able to validate that the store reflect the proper state, however the UI component only gets updated at every second call.
What am I missing here?
ApolloClient.subscribe's next function is very similar to how updateQueries works in Apollo Client’s mutate function, but with the exception that cache.writeQuery does not broadcast the changes if it is not called from the the Mutation's update function.
SOLUTION: use client.writeQuery(...) instead of cache.writeQuery(...)
Note: The update function receives cache rather than client as its
first parameter. This cache is typically an instance of InMemoryCache,
as supplied to the ApolloClient constructor when the client was
created. In case of the update function, when you call
cache.writeQuery, the update internally calls broadcastQueries, so
queries listening to the changes will update. However, this behavior
of broadcasting changes after cache.writeQuery happens only with the
update function. Anywhere else, cache.writeQuery would just write to
the cache, and the changes would not be immediately broadcast to the
view layer. To avoid this confusion, prefer client.writeQuery when
writing to cache.
Source: https://www.apollographql.com/docs/react/essentials/mutations/#updating-the-cache

Extend all views with custom data and filters in Sails.js

I'm developing a Sails.js application and I want to extend all views with custom data and functions.
What would be the best course of action to do so?
I've tried to create a policy to do so, but policies are only applied to routes with controllers.
Custom data
You can use a custom hook in order to achieve that.
Create a file at the specified path: api/hooks/viewsGlobals/index.js with the following content:
module.exports = function viewsGlobals (sails) {
return {
routes: {
before: {
// This middleware will be executed for every request.
'*': function (req, res, next) {
// Using condition to filter out static file requests.
if (req.accepted.some(function (type) {
return type.value === 'text/html';
})) {
res.locals.someData = {
// Place your custom data here.
};
}
return next();
}
}
}
}
};
Custom filters
Create a file at the following path: config/view-filters/toUpper.js and the following content:
// Replace this with templating engine that you use.
var swig = require('swig');
// Use the API of your templating engine to add custom filter.
swig.setFilter('toUpper', function (value) {
return value.toUpperCase();
});

How to mock a Node.js module loaded with dojo/node

I have an application with the server code running on Node.js and using Dojo. I have a config module defined like:
define([
'dojo/node!nconf',
'dojo/_base/config'
], function (nconf, dojoConfig) {
nconf.argv().file({
file: dojoConfig.baseDir + '/config.json'
});
console.log('-- file name:', dojoConfig.baseDir + '/config.json');
console.log('-- context:', nconf.get('context'));
// ... logic here ...
return nconf.get(nconf.get('context'));
});
To be able to unit test this module, I've written two mocks: one for the nconf native module and one for dojoConfig. Here is the test:
define([
'require',
'intern!object',
'intern/chai!assert'
], function (require, registerSuite, assert) {
registerSuite({
name: 'config utility',
'load default settings': function () {
require.undef('dojo/node!nconf');
require.undef('dojo/_base/config');
require({ map: {
'*': {
'dojo/node!nconf': 'server/utils/tests/nconfMock',
'dojo/_base/config': 'server/utils/tests/dojoConfigMock'
}
}});
require(['../config', './nconfMock'], this.async(1000).callback(
function (config, nconfMock) {
assert.isNotNull(config);
assert.isNotNull(nconf);
// assert.deepEqual(config, nconfMock.contextSettings.test);
}
));
}
});
});
I can see that my mock of dojoConfig is correctly loaded, but not the mock of the nconf module. During a webcast on Intern, Dylan mentioned that the mapping does not consider the plugin, that there's the way to force dojo/node module to load this nconfMock. Would you mind to give me more details?
Obviously, this is verbose, so if this continues to be a common request, we’ll probably do something to make it simpler in the future.
Important note: Without mapping dojo/node to intern/node_modules/dojo/node, the loading of my initial config module as defined above fails in the Intern environment. The mapping is done in the intern.js file. The reported error is:
Error: node plugin failed to load because environment is not Node.js
at d:/git/fco2/src/libs/dojo/node.js:3:9
at execModule (d:\git\fco2\node_modules\intern\node_modules\dojo\dojo.js:512:54)
at d:\git\fco2\node_modules\intern\node_modules\dojo\dojo.js:579:7
at guardCheckComplete (d:\git\fco2\node_modules\intern\node_modules\dojo\dojo.js:563:4)
at checkComplete (d:\git\fco2\node_modules\intern\node_modules\dojo\dojo.js:571:27)
at onLoadCallback (d:\git\fco2\node_modules\intern\node_modules\dojo\dojo.js:653:7)
at d:\git\fco2\node_modules\intern\node_modules\dojo\dojo.js:758:5
at fs.js:266:14
at Object.oncomplete (fs.js:107:15)
Solution: As suggested by Colin Snover below, I now use Mockery. I also do NOT use the contextual require, only the default one. Here is a (simplified) solution working with the version 1.9.3 of the Dojo toolkit.
define([
'intern!object',
'intern/chai!assert',
'intern/node_modules/dojo/node!mockery',
'./nconfMock'
], function (registerSuite, assert, mockery, nconfMock) {
registerSuite({
name: 'config utility',
teardown: function () {
mockery.disable();
mockery.deregisterAll();
require({ map: { '*': { 'dojo/_base/config': 'dojo/_base/config' } } });
require.undef('dojo/_base/config');
require.undef('server/utils/config');
},
'load default settings': function () {
mockery.enable();
mockery.registerMock('nconf', nconfMock);
require({ map: { '*': { 'dojo/_base/config': 'server/utils/tests/dojoConfigMock' } } });
require.undef('dojo/_base/config');
require.undef('server/utils/config');
require(
['server/utils/config'],
this.async(1000).callback(function (config) {
assert.isNotNull(config);
assert.deepEqual(config, nconfMock.contextSettings.test);
})
);
}
});
});
Thanks, Dom
In order to mock a Node.js dependency, you will probably want to simply use one of the various available projects for mocking Node.js modules. Mockery is a good choice since it’s stand-alone.
Since it looks like you’re using dojo/node and not the one from Intern, in your case, you’d do it like this:
define([
'intern!object', 'dojo/node!mockery', 'dojo/Deferred', 'require'
], function (registerSuite, mockery, Deferred, require) {
var moduleUsingMock;
registerSuite({
setup: function () {
var dfd = new Deferred();
mockery.enable();
mockery.registerMock('module-to-mock', mockObject);
require([ 'module-using-mock' ], function (value) {
moduleUsingMock = value;
dfd.resolve();
});
return dfd.promise;
},
teardown: function () {
mockery.disable();
},
'some test': function () {
moduleUsingMock.whatever();
// ...
}
});
});