How to create singleton resource in feathers with mongoose? - javascript

I need a resource that wouldn't be a collection but single item instead. I don't see anything about customizing mongoose service in that way.

You can return anything from your find method, it does not have to be a collection. So to get an object for e.g. /singleton you can just do something like:
app.use('/singleton', {
find: function(params) {
return Promise.resolve({
test: 'data'
});
}
});
This will of course also work via a websocket socket.emit('singleton::find'). For the Mongoose service there are two options:
1) Extending
Extend the service and then call it with a single object like this:
const MongooseService = require('feathers-mongoose').Service;
class SingletonService extends MongooseService {
find(params) {
return super.find(params).then(data => data[0]);
}
}
app.use('/singleton', new SingletonService({
Model: Todo,
name: 'todo'
}));
2) Hooks
Potentially even nicer with feathers-hooks, register an after hook that retrieves the singleton item from the collection originally requested:
const hooks = require('feathers-hooks');
app.configure(hooks())
.use('/singleton', mongooseService('todo', Todo));
app.service('singleton').hooks({
after: {
find(hook) {
const firstItem = hook.result[0];
hook.result = firstItem;
}
}
});

Related

what is the difference between this.query and this.query.find()? I'm finding it confusing

I'm currently following a MERN tutorial wherein I'm building a complete E-commerce application.
This is the class which is used for querying and other purposes.
constructor(query, querystr) {
this.query = query;
this.querystr = querystr;
console.log(this.querystr.keyword);
}
search() {
const keyword = this.querystr.keyword
? {
name: {
$regex: this.querystr.keyword,
$options: "i",
},
}
: {};
this.query = this.query.find({ ...keyword });
return this;
}
}
export default FeaturesApi;
This is the controller from where a new instance of class in being called.
export const getallProducts = catchAsyncerror(async (req, res, next) => {
const apifeature = new FeaturesApi(Product.find(), req.query).search();
const products = await FeaturesApi.query;
if (!products) {
return next(CustomError.noProducts);
}
res.status(200).json({ message: "Products found", products });
return next(CustomError);
});
can anybody pls tell me what is the difference between this.query and this.query.find() as used in the above code? As I understand it, this.query should itself be Product.find(), so this.query.find() would make it Product.find().find() right? Where am I going wrong? Pls help
this.query refers to the query property of an instance of the FeaturesApi class. Initially, it is assigned the value of Product.find(), which is a Mongoose method that returns a Query object that you can use to search and retrieve documents from a MongoDB collection.
In the search method of the FeaturesApi class, you are chaining the find method onto the this.query property:
this.query = this.query.find({ ...keyword });
This returns a new Query object that is filtered based on the keyword condition.
So, the difference between this.query and this.query.find() is that the former refers to the original Query object returned by Product.find(), while the latter refers to a new Query object that is filtered based on the keyword condition.

How to combine multiple stores as Root Store in Mobx and get access of in each other's fields and functions

I don't know how to combine two stores in one RootStore (using Typescript).
For example, I have liveImageStore.ts (with methods which request image, also holds array of last 10 image's urls) and notificationStore.ts (there are some logic to set/clear notification for further using throughout in whole application. And request in liveImageStore.ts provide some errors (ex., in case of http connection troubles). I would like to call function from notificationStore.ts (setNotification which push in store new notification) in request method from first store. But how to forward and type it all?
Notifier component show message use notificationStore.
This is how I'm using the root store pattern:
class RootStore {
childStoreOne: ChildStoreOne
childStoreTwo: ChildStoreTwo
constructor() {
this.childStoreOne = new ChildStoreOne(this)
this.childStoreTwo = new ChildStoreTwo(this)
}
}
class ChildStoreOne {
root: RootStore
constructor(root: RootStore) {
this.root = root
}
methodOne() {}
}
class ChildStoreTwo {
root: RootStore
constructor(root: RootStore) {
this.root = root
}
getSomethingFromStoreOne() {
this.root.childStoreOne.methodOne()
}
}
And this is the React part:
// holds a reference to the store (singleton)
let store: RootStore
// create the context
const StoreContext = createContext<RootStore | undefined>(undefined);
// create the provider component
function RootStoreProvider({ children }: { children: ReactNode }) {
//only create the store once ( store is a singleton)
const root = store ?? new RootStore()
return <StoreContext.Provider value={root}>{children}</StoreContext.Provider>
}
// create the hook
function useRootStore() {
const context = useContext(StoreContext)
if (context === undefined) {
throw new Error("useRootStore must be used within RootStoreProvider")
}
return context
}
Just make sure you don't access other stores in the constructor functions, because of the initialization order some child store might not be created at that point.
I've written a post about this pattern a while ago:
Mobx root store pattern with react hooks you will also find a link to a demo project with Nextjs.

Apollo Server: pass arguments to nested resolvers

My GraphQL query looks like this:
{
p1: property(someArgs: "some_value") {
id
nestedField {
id
moreNestedField {
id
}
}
}
}
On the server side, I'm using Apollo Server.
I have a resolver for the property and other resolvers for nestedField and moreNestedField.
I need to retrieve the value of someArgs on my nested resolvers.
I tried to do this using the context available on the resolver:
property: (_, {someArgs}, ctx) => {
ctx.someArgs = someArgs;
// Do something
}
But this won't work as the context is shared among all resolvers, thus if I have multiple propertyon my query, the context value won't be good.
I also tried to use the path available on info on my nested resolvers. I'm able to go up to the property field but I don't have the arguments here...
I also tried to add some data on info but it's not shared on nested resolvers.
Adding arguments on all resolvers is not an option as it would make query very bloated and cumbersome to write, I don't want that.
Any thoughts?
Thanks!
Params can be passed down to child resolvers using the currently returned value. Additional data will be removed from the response later.
I'll 'borrow' Daniel's code, but without specific params - pass args down as reference (suitable/cleaner/more readable for more args):
function propertyResolver (parent, args) {
const property = await getProperty()
property.propertyArgs = args
return property
}
// if this level args required in deeper resolvers
function nestedPropertyResolver (parent, args) {
const nestedProperty = await getNestedProperty()
nestedProperty.propertyArgs = parent.propertyArgs
nestedProperty.nestedPropertyArgs = args
return nestedProperty
}
function moreNestedPropertyResolver (parent) {
// do something with parent.propertyArgs.someArgs
}
As Daniels stated this method has limited functionality. You can chain results and make something conditionally in child resolver. You'll have parent and filtered children ... not filtered parent using child condition (like in SQL ... WHERE ... AND ... AND ... on joined tables), this can be done in parent resolver.
Do not pass your argument through root, except IDs or parent object, anything from client, use field level argument.
Please check this answer here on how to pass the arguments:
https://stackoverflow.com/a/63300135/11497165
To simplify it, you can put args in your field:
Example Type Definition
Server defination:
type Query{
getCar(color: String): Car
... other queries
}
type Car{
door(color: String): Door // <-- added args
id: ID
previousOwner(offset: Int, limit: Int): Owner // <-- added args
...
}
client query:
query getCar(carId:'123'){
door(color:'grey') // <-- add variable
id
previousOwner(offset: 3) // <-- added variable
... other queries
}
You should be able to access color in your child resolver arguments:
In your resolver:
Car{
door(root,args,context){
const color = args.color // <-- access your arguments here
}
previousOwner(root,args,context){
const offset = args.offset // <-- access your arguments here
const limit = args.limit // <-- access your arguments here
}
...others
}
For your example:
it will be like this
{
p1: property(someArgs: "some_value") { // <-- added variable
id
nestedField(someArgs: "some_value") { // <-- added variable
id
moreNestedField(offset: 5) {
id
}
}
}
}
You can pass the value through the parent field like this:
function propertyResolver (parent, { someArgs }) {
const property = await getProperty()
property.someArgs = someArgs
return property
}
function nestedPropertyResolver ({ someArgs }) {
const nestedProperty = await getNestedProperty()
nestedProperty.someArgs = someArgs
return nestedProperty
}
function moreNestedPropertyResolver ({ someArgs }) {
// do something with someArgs
}
Note that while this works, it may also point to an underlying issue with your schema design in the first place. Depending on how you're resolving these fields (getting them from a database, making requests to another API, etc.), it may be preferable to take a different approach altogether -- for example, by eager loading everything inside the root resolver. Without more context, though, it's hard to make any additional recommendations.

Delete properties from object that are not in a class

I'm using adonisjs, and am trying to sanitize a post request on the server. The data I get back has extra properties that are not mapped to the table/model so it is erroring when I try to save. Here is the update method
async update ({ params, request, response }) {
const contract = await Contract.find(params.id);
contract.merge(request.post());
return await contract.save();
}
The problem is that when I returned the contract earlier on a get request, I added some computed properties. I could do something along the lines of
const { prop1, prop2 } = request.post();
but that doesn't feel like a future proof or clean solution. Ideally I want the object to only have the properties defined on the table/model. I have a validator setup as described in the validator docs, but it still lets other properties bypass it.
I resolved this by adding a beforeSave hook in the model class that filter's properties on the object, which allows us to keep a thin controller.
const FIELDS = ['id', 'description'];
class Contract extends Model {
static boot() {
super.boot();
this.addHook('beforeSave', async contractInstance => {
Object.keys(contractInstance.$attributes).forEach(key => {
if (!CONTRACT_FIELDS.includes(key)) {
delete contractInstance.$attribute[key];
}
});
});
}
}
What about an helper class?
class RequestContractExtractor{
static desiredProps = [
"prop1",
"prop2",
]; // you could replace this with a list you get from your model class
constructor(requestData){
desiredProps.forEach(prop => this[prop] = requestData[prop]);
}
static from(...args){ return new this(...args); }
}
Then you'd be able to do:
async update ({ params, request, response }) {
const contract = await Contract.find(params.id);
contract.merge(RequestContractExtractor.from(request.post()));
return await contract.save();
}
I resolved this by adding a beforeSave hook that filter's properties on the object.
Create a file at /app/Model/Hooks/ContractHook.js
'use strict'
const ContractHook = module.exports = {}
const CONTRACT_FIELDS = ["id", "description"];
ContractHook.removeDynamicFields = async (contractInstance) => {
if (contractInstance) {
Object.keys(contractInstance.$attributes).forEach((key) => {
if (!CONTRACT_FIELDS.includes(key) && contractInstance.$attributes[key]) {
delete contractInstance.$attributes[key];
}
});
}
};
Use it in your model like so...
class Contract extends Model {
static boot() {
super.boot();
this.addHook("beforeCreate", [
"ContractHook.removeDynamicFields",
]);
}

Can't Access Imported Classes in React Native

For some reason we're having a ton of trouble using classes/prototypes in react native... we're not sure if we're doing something wrong, if es6 isn't actually supported or what. How can we use classes in react native? Clearly we aren't doing something right.
What we've tried
Creating a function and adding prototypes to it and exporting at the bottom
Creating and exporting a class with a constructor
Importing with {} and without, and exporting with default
The errors
Db is not a constructor
_Db2.default is not a constructor
Cannot read property 'default' of undefined
_Db.Db is not a constructor
No matter what we've tried, we cannot import an object of our creation and instantiate it. Here is an example of the prototype we've set up in another stackoverflow post we made when trying to untangle the issue
Here is an example of how we're importing it in.
import Db from '../localstorage/db/Db';
//var Db = require('../localstorage/db/Db');
const db = new Db();
When using require, it seems like the import statement works and an attribute we assign in the constructor exists, but none of the other prototypes are in the object.
EDIT 1: Below is our class implementation. We are instantiating realm outside of the class because realm seems to crash when instantiated inside of a class as documented in this github issue.
const realm = new Realm({
schema: [Wallet, WalletAddress, WalletTransaction, Log, APIWallet, APITransaction, APIAccount, Configuration],
path: config.db_path
});
export default class Db extends Object {
constructor() {
super();
this.realm = realm;
logger(2, realm.path);
}
//https://realm.io/docs/javascript/latest/#to-many-relationships
doOneToMany(one, many) {
many.forEach(m => {
this.write(() => {
one.push(m);
});
});
}
query(model, filter) {
let results = this.realm.objects(model);
if (filter) {
return results.filtered(filter);
}
return results;
}
insert(model, options) {
if (options == undefined && model instanceof Realm.Object) {
this.write(() => {
realm.create(model);
});
} else {
this.write(() => {
realm.create(model, options);
});
}
}
update(obj, options) {
this.write(() => {
Object.keys(options).map((key, attribute) => {
obj[key] = attribute;
});
});
}
del(model, obj) {
this.write(() => {
realm.delete(obj);
});
}
write(func) {
try {
realm.write(func);
} catch (e) {
logger(0, e);
throw new Error('Db.js :: Write operation failed ::', e);
}
}
close() {
Realm.close();
}
}
//module.exports = { Db };
The answer was we had a circular dependency in a Logger.js file that required Db.js while Db.js required Logger for useful logging. Removing the circular dependency caused classes and all the other import issues to go away.

Categories