Use JavaScript Proxy to prepend value to argument - javascript

I'm using Axios to work with my REST API. The API serves multiple apps with routes of the form /api/<app>/....
In my "mp" app client, I'm constantly repeating the string Axios.[rest-method]('/api/mp/[...]', ...). I thought a clever way to avoid this would be to wrap Axios in a Proxy. With this proxied object, I could make my calls like Axios.mp.get(...) and it would automatically prepend /api/mp to the first argument of get().
Obviously, I could create my own wrapper object with wrapped REST methods that did this, but where's the fun in that?
Here's what I have so far:
import AxiosLib from 'axios';
const routes = {
mp: '/api/mp',
// ...
},
restMethods = new Set([
'get',
'post',
'put',
'delete',
'head',
'patch'
]);
const Axios = new Proxy(AxiosLib, {
get: function(target, prop, receiver) {
let routePrefix = routes[prop];
if (routePrefix) {
// return `receiver` with magic to store `routePrefix`
} else if (restMethods.has(prop)) {
let method = Reflect.get(target, prop, receiver);
routePrefix = /* get magically stored `routePrefix` if it exists */ || '';
return function(url, ...args) {
return method.apply(this, routePrefix + url, ...args);
}
} else {
return Reflect.get(target, prop, receiver);
}
}
});
export default Axios;
What do I put in the two commented sections?
I could create a new, second-layer Proxy for each defined route that handled this, but for cleanliness's/simplicity's sake, I'd like to use only a single proxy.

Related

Sending some but not all args to a function without defining nulls

I'm using vue 3 and composable files for sharing some functions through my whole app.
My usePluck.js composable file looks like
import { api } from 'boot/axios'
export default function usePlucks() {
const pluckUsers = ({val = null, excludeIds = null}) => api.get('/users/pluck', { params: { search: val, exclude_ids: excludeIds }})
return {
pluckUsers
}
}
In order to make use of this function in my component I do
<script>
import usePlucks from 'composables/usePlucks.js'
export default {
name: 'Test',
setup() {
const { pluckUsers } = usePlucks()
onBeforeMount(() => {
pluckUsers({excludeIds: [props.id]})
})
return {
}
}
}
</script>
So far so good, but now I'd like to even be able to not send any args to the function
onBeforeMount(() => {
pluckUsers()
})
But when I do that, I get
Uncaught TypeError: Cannot read properties of undefined (reading 'val')
I assume it's because I'm not sending an object as argument to the function, therefore I'm trying to read val from a null value: null.val
What I'm looking for is a way to send, none, only one, or all arguments to the function with no need to set null values:
// I don't want this
pluckUsers({})
// Nor this
pluckUsers({val: null, excludeIds: [props.id]})
I just want to send only needed args.
Any advice about any other approach will be appreciated.
import { api } from 'boot/axios'
export default function usePlucks() {
const pluckUsers = ({val = null, excludeIds = null} = {}) => api.get('/users/pluck', { params: { search: val, exclude_ids: excludeIds }})
return {
pluckUsers
}
}
I believe this is what you're looking for. The { ... } = {}
EDIT: It didn't work without this because with no argument the destructuring failed because it can't match an object. That's why you also need a default value on the parameter object, also called simulated named parameter.

How to read instance decorators in typescript?

I can create custom decorator using reflect-metadata and it work fine.
Problem is, that I don`t know how to get all instance decorators.
import 'reflect-metadata';
console.clear();
function readTypes() {
const decorator: MethodDecorator = (target, propertyKey, description) => {
const args = Reflect.getMetadata(
'design:paramtypes',
target,
propertyKey
).map(c => c.name);
const ret = Reflect.getMetadata('design:returntype', target, propertyKey);
console.log(`Arguments type: ${args.join(', ')}.`);
console.log(`Return type: ${ret.name}.`);
};
return decorator;
}
class Foo {}
class Bar {
#readTypes()
public fn(a: number, b: string, c: Foo): boolean {
return true;
}
}
const barInstance = new Bar();
I would like to get all functions with decorator #readTypes from barInstance. How can I do it?
See working example:
https://stackblitz.com/edit/decorators-metadata-example-nakg4c
First of all, you aren't writing any metadata, only reading it. If you want to lookup which properties were decorated, then you have to write metadata to those properties.
For simplicity's sake, let's simplify the decorator to:
// It's a best practice to use symbol as metadata keys.
const isReadTypesProp = Symbol('isReadTypesProp')
function readTypes() {
const decorator: MethodDecorator = (target, propertyKey, description) => {
Reflect.defineMetadata(isReadTypesProp, true, target, propertyKey);
};
return decorator;
}
Now when the decorator is used, it's executed when the class is parsed, not when instances are created. This means that target in the decorator function is actually the prototype of the class constructor function.
In other words target is the same object as
Bar.prototype or barInstance.constructor.prototype or barInstance.__proto__.
Knowing that we can loop over all the property names in the prototype object and look up that metadata we set earlier:
function getReadTypesPropsFromInstance(object: {
constructor: {
prototype: unknown;
};
}) {
const target = object.constructor.prototype;
const keys = Object.getOwnPropertyNames(target);
return keys.filter(key => Reflect.getMetadata(isReadTypesProp, target, key));
}
Which now returns the names of the decorated properties:
const barInstance = new Bar();
console.log(getReadTypesPropsFromInstance(barInstance)); // ["fn"]
Working example

why toUpperCase not working in nextjs while adding slug

I am following this link
https://nextjs.org/docs/api-reference/next.config.js/headers
adding x-slug key .I am adding like this
module.exports = {
async headers() {
return [
{
source: '/:slug',
headers: [
{
key: 'x-slug',
value: `${abc(':slug')}` // Matched parameters can be used in the value
}
]
}
];
}
};
when I using this url
https://nextjs-vetexh--3000.local.webcontainer.io/dsd
"slug" --- > dsd
but when I using toUppercase() function it is not working why
function abc(a) {
// working
// return a;
// not working
return a.toUpperCase();
}
It is giving SLUG in capital .
Expected output is "DSD"
here is my code
https://stackblitz.com/edit/nextjs-vetexh?file=next.config.js
function abc(a) {
// working
// return a;
// not working
return a.toUpperCase();
}
module.exports = {
async headers() {
return [
{
source: '/:slug',
headers: [
{
key: 'x-slug',
value: `${abc(':slug')}` // Matched parameters can be used in the value
}
]
}
];
}
};
It's probably not possible in next.config.js, but there are other options with some tradeoffs.
With getServerSideProps
You could add custom HTTP headers by matching the path for the request using getServerSideProps in _app.js:
export async function getServerSideProps(context) {
// set HTTP header
context.res.setHeader('x-slug', upperCase(context.params.slug))
return {
props: {}, // will be passed to the page component as props
}
}
The context parameter is an object containing the following keys:
params: If this page uses a dynamic route, params contains the route parameters. If the page name is [id].js , then params will look like { id: ... }.
req: The HTTP IncomingMessage object.
res: The HTTP response object.
query: An object representing the query string.
But using getServerSideProps would disable static optimization as all pages will be only server-side rendered.
More on Server-side Rendering
With Custom Server
Another way is to use a Custom Server and override the response headers.
server.get('/:slug*', (req, res) => {
res.set('x-slug', upperCase(req.params['slug']));
handle(req, res, parsedUrl);
});
A custom server will remove important performance optimizations, like serverless functions and Automatic Static Optimization.

Specifing type of ES6 destructured parameters in Flow

In ES6, you can use named parameters, e.g.
// #flow
const makeRequest = (dateFrom: Object, dateTo: Object, subdomain: string, {shortId, label} = {}) => {
let request = {}
try {
dateValidation(dateFrom, dateTo)
subdomainValidation(subdomain)
request.subdomain = subdomain
request.dateFrom = dateFrom.format('YYYY-MM-DD')
request.dateTo = dateTo.format('YYYY-MM-DD')
if (shortId) {
request.shortId = shortId
}
if (label) {
request.label = label
}
} catch (error) {
throw error
}
return request
}
ref: http://www.2ality.com/2011/11/keyword-parameters.html
it is a part of my code, and as you can see I used {shortId, label} = {} as an parameter, which means i want parameters named shortId, label, but it is optional!('cause initialized by {})
The point is, I want to use Flow as a static type checker, like I did in dateFrom: Object. But if I do this {shortId, label}: Object = {}, ESLint says Parsing Error: binding rvalue.
So, how can I use Flow, ES6 named parameters at once?

How to find a record both by id and query parameters in Ember

I'm trying to use ember-data to send a request via id and query parameters to an endpoint. The end output of the ajax call would be http://api.example.com/invoices/1?key=value. As far as I know, ember-data's store doesn't have a native way to find by both id and query parameters (neither of the following worked):
// outputs http://api.example/com/invoices/1
this.store.find('invoice', 1);
// outputs http://api.example.com/invoices?id=1&key=value
this.store.find('invoice, {id: 1, key: value});
Instead, I've been attempting to modify the invoice adapter. Our backend is Django, so we're using the ActiveModelAdapter. I want to override the method that builds the url so that if id is present in the query object, it will automatically remove it and append it to the url instead before turning the rest of the query object into url parameters.
The only problem is that I can't figure out which method to override. I've looked at the docs for ActiveModelAdapter here, and I've tried overriding the findRecord, buildUrl, urlForFind, and urlForQuery methods, but none of them are getting called for some reason (I've tried logging via console.log and Ember.debug). I know the adapter is working correctly because the namespace is working.
Here's my adapter file:
import DS from 'ember-data';
import config from '../config/environment';
export default DS.ActiveModelAdapter.extend({
namespace: 'v1',
host: config.apiUrl,
// taken straight from the build-url-mixin and modified
// very slightly to test for logging
urlForFindRecord: function(id, modelName, snapshot) {
Ember.debug('urlForFindRecord is being called');
if (this.urlForFind !== urlForFind) {
Ember.deprecate('BuildURLMixin#urlForFind has been deprecated and renamed to `urlForFindRecord`.');
return this.urlForFind(id, modelName, snapshot);
}
return this._buildURL(modelName, id);
},
// taken straight from the build-url-mixin and modified
// very slightly to test for logging
findRecord: function(store, type, id, snapshot) {
Ember.debug('findRecord is being called');
var find = RestAdapter.prototype.find;
if (find !== this.find) {
Ember.deprecate('RestAdapter#find has been deprecated and renamed to `findRecord`.');
return this.find(store, type, id, snapshot);
}
return this.ajax(this.buildURL(type.modelName, id, snapshot, 'findRecord'), 'GET');
},
// taken straight from the build-url-mixin and modified
// very slightly to test for logging
urlForQuery: function(query, modelName) {
Ember.debug('urlForQuery is being called');
if (this.urlForFindQuery !== urlForFindQuery) {
Ember.deprecate('BuildURLMixin#urlForFindQuery has been deprecated and renamed to `urlForQuery`.');
return this.urlForFindQuery(query, modelName);
}
return this._buildURL(modelName);
},
// taken straight from the build-url-mixin and modified
// very slightly to test for logging
_buildURL: function(modelName, id) {
Ember.debug('_buildURL is being called');
var url = [];
var host = get(this, 'host');
var prefix = this.urlPrefix();
var path;
if (modelName) {
path = this.pathForType(modelName);
if (path) { url.push(path); }
}
if (id) { url.push(encodeURIComponent(id)); }
if (prefix) { url.unshift(prefix); }
url = url.join('/');
if (!host && url && url.charAt(0) !== '/') {
url = '/' + url;
}
return url;
},
});
Is there an easier way to accomplish what I'm trying to do without overriding adapter methods? And if not, what method(s) do I need to override?
Thanks in advance for your help!
You can use this.store.findQueryOne('invoice', 1, { key: value });
https://github.com/emberjs/data/pull/2584

Categories

Resources