I define my api with something like the below:
class MyFeathersApi {
feathersClient: any;
accountsAPI: any;
productsAPI: any;
constructor(app) {
var port: number = app.get('port');
this.accountsAPI = app.service('/api/accounts');
this.productsAPI = app.service('/api/products');
}
findAdminAccounts(filter: any, cb: (err:Error, accounts:Models.IAccount[]) => void) {
filter = { query: { adminProfile: { $exists: true } } }
this.accountsAPI.find(filter, cb);
}
When I want to use database adapter methods, from the client, i.e. find and/or create, I do the below:
var accountsAPIService = app.service('/api/accounts');
accountsAPIService.find( function(error, accounts) {
...
});
How I call custom methods, such as findAdminAccounts() from the client?
You can only use the normal service interface on the client. We found that support for custom methods (and all the issues it brings with it going from a clearly defined interface to arbitrary method names and parameters) is not really necessary because everything in itself can be described as a resource (service).
The benefits (like security, predictability and sending well defined real-time events) so far have heavily outweighed the slight change in thinking required when conceptualizing your application logic.
In your example you could make a wrapper service that gets the admin accounts like this:
class AdminAccounts {
find(params) {
const accountService = this.app.service('/api/accounts');
return accountService.find({ query: { adminProfile: { $exists: true } } });
}
setup(app) {
this.app = app;
}
}
app.use('/api/adminAccounts', new AdminAccounts());
Alternatively you could implement a hook that maps query parameters to larger queries like this:
app.service('/api/accounts').hooks({
before: {
find(hook) {
if(hook.params.query.admin) {
hook.params.query.adminProfile = { $exists: true };
}
}
}
});
This would now allow calling something like /api/accounts?admin.
For more information see this FAQ.
Related
Is there some npm package for memoizing object lazily, so that the first attempt to access it would load it?
The problem:
// service
class Service {
private readonly pathMap = {
user: process.env.USER_PATH,
post: process.env.POST_PATH,
page: process.env.PAGE_PATH,
}
getPath(entityType: EntityType) {
return this.pathMap[entityType];
}
}
export const service = new Service();
// service.spec.ts
import { service } from './service';
import { loadEnvVars } from '#app/loadEnvVars';
describe('service', () => {
beforeAll(loadEnvVars);
it('should return path', () => {
expect(service.getPath('user')).toBe(process.env.USER_PATH);
expect(service.getPath('post')).toBe(process.env.POST_PATH);
expect(service.getPath('page')).toBe(process.env.PAGE_PATH);
});
});
The tests will fail because the singleton service will load before the loadEnvVars due to the import of the service (before the beforeAll), which means the env vars will be set to undefined in the service pathMap.
The proposed solution:
I know there are several ways to fix it, but IMO the best solution would be to somehow lazy load the pathMap object so that the first attempt to get something from it will actually init the variable's value.
Here's a function I wrote to tackle this (Typescript):
export function lazy<T extends (...args: any[]) => any>(factory: T): ReturnType<T> {
let obj: ReturnType<T> | undefined;
const proxy = new Proxy(
{},
{
get(_, key) {
if (!obj) {
obj = factory();
}
return obj[key];
}
}
);
return proxy as ReturnType<T>;
}
Now the service will look like this instead:
class Service {
private readonly pathMap = lazy(() => ({
user: process.env.USER_PATH,
post: process.env.POST_PATH,
page: process.env.PAGE_PATH,
}));
getPath(entityType: EntityType) {
return this.pathMap[entityType];
}
}
export const service = new Service();
Now the tests will pass.
Note: In this solution lazy returns a read-only object. It can be changed of course.
The question:
Is there some NPM library out there that provides something like that already? Cause if not I think I might publish it myself.
I have two classes, A and B. What I am trying to do is to pass data from A to B after receiving a message from sockets.
This is simplified look of how classes are defined:
class A:
export default class A {
client;
callbacks;
constructor() {
this.callbacks = {
open: () => this.client.logger.debug('open'),
close: () => this.client.logger.debug('closed'),
message: (data) => {this.client.logger.log(data)}, //I want to pass this data object to class B
};
this.client = new Spot(constants.apiKey, constants.apiSecret, {
baseURL: constants.baseURL,
wsURL: constants.wsURL,
});
this.client.userData(listenKey, this.callbacks);
}
}
I already have a property of A in class definition of B:
export default class B {
account;
constructor() {
this.account = new A();
}
}
What would be a correct/standard way to connect these two so I get a 'data' object from class A every time the socket message callback from class A is triggered?
I am a bit new with JS, but on iOS we would use a delegation pattern, with a protocol, that says:
class A will have a delegate property.
A delegate (class B) must implement a protocol (in this case it would be a requirement to implement method called didReceiveMessage(data).
After that, when a message is received in class A, we would just do(in socket message callback shown above) something like this.delegate.didReceiveMessage(data).
Protocol usage here is not important generally, but it is a plus, cause from A class, we can only access didReceiveData(data) method trough a delegate property, and nothing else (other properties / methods of class B are not visible). At least that is how it works in Swift/Obj-C. I just mentioned it, cause I am curious is this how it is done in JS too.
I guess there is some similar mechanism in Javascript, or some more standard/better way to achieve this kind of data sending between objects?
on iOS we would use a delegation pattern, with a protocol
You can do it exactly as you described:
export default class A {
client;
delegate;
constructor(delegate) {
this.delegate = delegate;
this.client = new Spot(constants.apiKey, constants.apiSecret, {
baseURL: constants.baseURL,
wsURL: constants.wsURL,
});
const callbacks = {
open: () => this.client.logger.debug('open'),
close: () => this.client.logger.debug('closed'),
message: (data) => this.delegate.didReceiveMessage(data),
};
this.client.userData(listenKey, callbacks);
}
}
export default class B {
account;
constructor() {
this.account = new A(this);
}
didReceiveMessage(data) {
console.log(data); // or whatever
}
}
There is no interface (protocol) declaration that would tell A which properties and methods it may access on the passed delegate, but the contract exists of course. You should document it in prose. (Or use TypeScript).
Notice also how your class A interacts with the Spot client, it uses very much the same pattern of passing an object with event handler methods.
A simpler pattern in JavaScript, if you just need a single method in your protocol, is to pass a callable function only:
export default class A {
client;
constructor(onMessage) {
this.client = new Spot(constants.apiKey, constants.apiSecret, {
baseURL: constants.baseURL,
wsURL: constants.wsURL,
});
this.client.userData(listenKey, {
open: () => this.client.logger.debug('open'),
close: () => this.client.logger.debug('closed'),
message: onMessage,
});
}
}
export default class B {
account;
constructor() {
this.account = new A(this.didReceiveMessage.bind(this));
// or inline:
this.account = new A(data => {
console.log(data); // or whatever
});
}
didReceiveMessage(data) {
console.log(data); // or whatever
}
}
I am not an expert on NodeJs, but you can use something like an emitter plugin.
In javascript, it would look like this:
function A() {
Emitter(this);
this.action = function() {
console.log("something happened");
this.emit("action", { prop: "value" });
};
}
function B(a_instance) {
// subscribe to "action" event
a.on("action", function(data) {
console.log(data.prop); // "value"
});
};
var myA = new A();
var myB = new B(myA);
myA.action();
I'm trying to write a Vue plugin that's a simple abstraction to manage auth state across my app. This will need to access other Vue plugins, namely vuex, vue-router and vue-apollo (at the moment).
I tried extending Vue.prototype but when I try to access the plugin's properties how I would normally - eg. this.$apollo - I get the scope of the object, and therefore an undefined error. I also tried adding vm = this and using vm.$apollo, but this only moves the scope out further, but not to the Vue object - I guess this is because there is no instance of the Vue object yet?
export const VueAuth = {
install (Vue, _opts) {
Vue.prototype.$auth = {
test () {
console.log(this.$apollo)
}
}
}
}
(The other plugins are imported and added via. Vue.use() in the main app.js)
Alternatively, I tried...
// ...
install (Vue, { router, store, apollo })
// ...
but as a novice with js, I'm not sure how this works in terms of passing a copy of the passed objects, or if it will mutate the originals/pass by ref. And it's also very explicit and means more overhead if my plugin is to reach out to more plugins further down the line.
Can anyone advise on a clean, manageable way to do this? Do I have to instead alter an instance of Vue instead of the prototype?
In the plugin install function, you do not have access to the Vue instance (this), but you can access other plugins via the prototype. For example:
main.js:
Vue.use(Apollo)
Vue.use(VueAuth) // must be installed after vue-apollo
plugin.js:
export const VueAuth = {
install (Vue) {
Vue.prototype.$auth = {
test () {
console.log(Vue.prototype.$apollo)
}
}
}
}
I found a simple solution for this issue:
In plugin installer you need to add value to not just prototype, but Vue itself to be able to use it globally.
There is a code example:
Installer:
import apiService from "../services/ApiService";
// Service contains 'post' method
export default {
install(Vue) {
Vue.prototype.$api = apiService;
Vue.api = apiService;
}
};
Usage in other plugin:
import Vue from "vue";
...
const response = await Vue.api.post({
url: "/login",
payload: { email, password }
});
Usage in component:
const response = await this.$api.post({
url: "/login",
payload: { email, password }
});
I'm not sure if that's a good solution, but that made my scenario work perfectly.
So, I got around this by converting my property from a plain ol' object into a closure that returns an object, and this seems to have resolved my this scoping issue.
Honestly, I've jumped into Vue with minimal JS-specific knowledge and I don't fully understand how functions and the likes are scoped (and I'm not sure I want to look under that rock just yet......).
export const VueAuth = {
install (Vue, opts) {
Vue.prototype.$auth = function () {
let apollo = this.$apolloProvider.defaultClient
let router = this.$router
return {
logIn: function (email, password) {
apollo.mutate({
mutation: LOGIN_MUTATION,
variables: {
username: email,
password: password,
},
}).then((result) => {
// Result
console.log(result)
localStorage.setItem('token', result.data.login.access_token)
router.go(router.currentRoute.path)
}).catch((error) => {
// Error
console.error('Error!')
console.error(error)
})
},
logOut: function () {
localStorage.removeItem('token')
localStorage.removeItem('refresh-token')
router.go()
console.log('Logged out')
},
}
}
It's a rudimental implementation at the moment, but it'll do for testing.
I am trying to figure out testing with Jest for my MobX stores.
I am using Mobx, React, and Jest.
class ConfigStore {
constructor(RootStore) {
this.rootStore = RootStore;
this.config = {};
}
}
class DataStore {
constructor(RootStore) {
this.config = RootStore.config;
}
}
class UIStore {
constructor(RootStore) {
this.config = RootStore.config;
this.data = RootStore.data;
}
}
class RootStore {
constructor() {
this.config = new ConfigStore(this);
this.ui = new UIStore(this);
this.data = new DataStore(this);
}
}
Did I set my stores up correctly?
If so, what is the best way to test the stores before they get passed to Provider?
Your question is very unclear. What exactly do you want to test about these stores in unit tests? You can't really test data itself.
My suggestions:
link to stores
instead of using setting a single property just keep the whole store:
class DataStore {
constructor(RootStore) {
this.configStore = RootStore;
}
}
this way you can besure properties are always properly updated and observed.
if you want you can always have property on your lower level stores:
class DataStore {
constructor(RootStore) {
this.configStore = RootStore;
}
get config() {
return this.configStore.config;
}
}
Abstract
if you use typescript abstract your stores with interfaces so the stores are way easilier tested:
class DataStore {
constructor(store: IConfigStore) {
this.configStore = store;
}
}
interface IConfigStore {
config: Config;
}
Use a repository pattern
For every store make a repository injectable so that all api calls done by the store are actually done in this repository:
class RootStore {
constructor(repository) {
this.repostiory = repository;
this.config = new ConfigStore(this);
this.ui = new UIStore(this);
this.data = new DataStore(this);
this.initializeData();
}
async initializeData(){
this.config = await this.repository.getConfig();
}
}
This way you can easily mock the repository to give static date so you dont need to do any api calls.
Keep your react components pure
The react components that you really want to unit test. make sure they dont use mobx stores directly but you use the inject() function instead to make a second class: https://github.com/mobxjs/mobx-react#inject-as-function
this way your components are way easilier testable and useable stand alone:
const PureReactComponent = ({ name }) => <h1>{name}</h1>
const SimpleMobxComponent = inject(stores => ({
name: stores.userStore.name
}))(PureReactComponent)
// usage:
render() {
return <SimpleMobxComponent/>
}
I'm diving into GraphQL and Relay. So far, everything has been relatively smooth and easy, for me, to comprehend. I've got a GraphQL schema going with Accounts and Teams. There is no relationships between the two, yet. I've got some Relay-specific GraphQL adjustments for connections, for both the accounts and teams. Here's an example query for those two connections ...
{
viewer {
teams {
edges {
node {
id,
name
}
}
}
accounts {
edges {
node {
id,
username
}
}
}
}
}
I've got a GraphQL mutation ready to go that creates a new account. Here's the GraphQL representation of that ...
type Mutation {
newAccount(input: NewAccountInput!): NewAccountPayload
}
input NewAccountInput {
username: String!
password: String!
clientMutationId: String!
}
type NewAccountPayload {
account: Account
clientMutationId: String!
}
type Account implements Node {
id: ID!
username: String!
date_created: String!
}
I'm now trying to create my client-side Relay mutation that uses this GraphQL mutation. I'm thoroughly confused as to how to do this correctly, though. I've followed examples and nothing I come up with even seems to run correctly. I tend to get errors relating to fragment composition.
If I were writing a Relay mutation that uses this GraphQL mutation, what would the appropriate mutator configuration be? Should I be using RANGE_ADD?
For your client side mutation, you can use something like this:
export default class AddAccountMutation extends Relay.Mutation {
static fragments = {
viewer: () => Relay.QL`
fragment on Viewer {
id,
}
`,
};
getMutation() {
return Relay.QL`mutation{addAccount}`;
}
getVariables() {
return {
newAccount: this.props.newAccount,
};
}
getFatQuery() {
return Relay.QL`
fragment on AddAccountPayload {
accountEdge,
viewer {
accounts,
},
}
`;
}
getConfigs() {
return [{
type: 'RANGE_ADD',
parentName: 'viewer',
parentID: this.props.viewer.id,
connectionName: 'accounts',
edgeName: 'accountEdge',
rangeBehaviors: {
'': 'append',
},
}];
}
getOptimisticResponse() {
return {
accountEdge: {
node: {
userName: this.props.newAccount.userName,
},
},
viewer: {
id: this.props.viewer.id,
},
};
}
}
Then, in your GraphQL schema, you'll need to return the newly created edge, as well as the cursor:
var GraphQLAddAccountMutation = mutationWithClientMutationId({
name: 'AddAccount',
inputFields: {
newAccount: { type: new GraphQLNonNull(NewAccountInput) }
},
outputFields: {
accountEdge: {
type: GraphQLAccountEdge,
resolve: async ({localAccountId}) => {
var account = await getAccountById(localAccountId);
var accounts = await getAccounts();
return {
cursor: cursorForObjectInConnection(accounts, account)
node: account,
};
}
},
viewer: {
type: GraphQLViewer,
resolve: () => getViewer()
},
},
mutateAndGetPayload: async ({ newAccount }) => {
var localAccountId = await createAccount(newAccount);
return {localAccountId};
}
});
var {
connectionType: AccountsConnection,
edgeType: GraphQLAccountEdge,
} = connectionDefinitions({
name: 'Account',
nodeType: Account,
});
You'll need to substitute the getAccounts(), getAccountById() and createAccount method calls to whatever your server/back-end uses.
There may be a better way to calculate the cursor without having to do multiple server trips, but keep in mind the Relay helper cursorForObjectInConnection does not do any kind of deep comparison of objects, so in case you need to find the account by an id in the list, you may need to do a custom comparison:
function getCursor(dataList, item) {
for (const i of dataList) {
if (i._id.toString() === item._id.toString()) {
let cursor = cursorForObjectInConnection(dataList, i);
return cursor;
}
}
}
Finally, add the GraphQL mutation as 'addAccount' to your schema mutation fields, which is referenced by the client side mutation.
Right now, I'm following a roughly 5 step process to define mutations:
Define the input variables based on what portion of the graph you are targeting - in your case, it's a new account, so you just need the new data
Name the mutation based on #1 - for you, that's AddAccountMutation
Define the fat query based on what is affected by the mutation - for you, it's just the accounts connection on viewer, but in the future I'm sure your schema will become more complex
Define the mutation config based on how you can intersect the fat query with your local graph
Define the mutation fragments you need to satisfy the requirements of #1, #3 and #4
Generally speaking, step #4 is the one people find the most confusing. That's because it's confusing. It's hard to summarize in a Stack Overflow answer why I feel this is good advice but... I recommend you use FIELDS_CHANGE for all your mutations*. It's relatively easy to explain and reason about - just tell Relay how to look up the nodes corresponding to the top level fields in your mutation payload. Relay will then use the mutation config to build a "tracked query" representing everything you've requested so far, and intersect that with the "fat query" representing everything that could change. In your case, you want the intersected query to be something like viewer { accounts(first: 10) { edges { nodes { ... } } }, so that means you're going to want to make sure you've requested the existing accounts somewhere already. But you almost certainly have, and if you haven't... maybe you don't actually need to make any changes locally for this mutation!
Make sense?
EDIT: For clarity, here's what I mean for the fat query & configs.
getConfigs() {
return [
{
type: "FIELDS_CHANGE",
fieldIDs: {
viewer: this.props.viewer.id
}
}]
}
getFatQuery() {
return Relay.QL`
fragment on AddAccountMutation {
viewer {
accounts
}
}
`
}
*addendum: I currently believe there are only one or two reasons not to use FIELDS_CHANGE. The first is that you can't reliably say what fields are changing, so you want to just manipulate the graph directly. The second is because you decide you need the query performance optimizations afforded by the more specific variants of FIELDS_CHANGE like NODE_DELETE, RANGE_ADD, etc.