-
-
Save DavidWells/93535d7d6bec3a7219778ebcfa437df3 to your computer and use it in GitHub Desktop.
/* Ultra lightweight Github REST Client */ | |
// original inspiration via https://gist.github.com/v1vendi/75d5e5dad7a2d1ef3fcb48234e4528cb | |
const token = 'github-token-here' | |
const githubClient = generateAPI('https://api.github.com', { | |
headers: { | |
'User-Agent': 'xyz', | |
'Authorization': `bearer ${token}` | |
} | |
}) | |
async function getRepo() { | |
/* GET /repos/{owner}/{repo} */ | |
return githubClient.repos.davidwells.analytics.get() | |
} | |
async function generateRepoFromTemplate({ template, repoName }) { | |
/* POST /repos/{template_owner}/{template_repo}/generate */ | |
return githubClient.repos[`${template}`].generate.post({ name: repoName }) | |
} | |
getRepo().then((repoInfo) => { | |
console.log('repo', repoInfo) | |
}) | |
function generateAPI(baseUrl, defaults = {}, scope = []) { | |
const callable = () => {} | |
callable.url = baseUrl | |
return new Proxy(callable, { | |
get({ url }, propKey) { | |
const method = propKey.toUpperCase() | |
const path = scope.concat(propKey) | |
if (['GET', 'POST', 'PUT', 'DELETE', 'PATCH'].includes(method)) { | |
return (data, overrides = {}) => { | |
const payload = { method, ...defaults, ...overrides } | |
switch (method) { | |
case 'GET': { | |
if (data) url = `${url}?${new URLSearchParams(data)}` | |
break | |
} | |
case 'POST': | |
case 'PUT': | |
case 'PATCH': { | |
payload.body = JSON.stringify(data) | |
} | |
} | |
console.log(`Calling: ${url}`) | |
console.log('payload', payload) | |
return fetch(url, payload).then((d) => d.json()) | |
} | |
} | |
return generateAPI(`${url}/${propKey}`, defaults, path) | |
}, | |
apply({ url }, thisArg, [arg] = []) { | |
const path = url.split('/') | |
return generateAPI(arg ? `${url}/${arg}` : url, defaults, path) | |
} | |
}) | |
} |
Removing apply
function and scope
argument and reorganising the function definition for easier understanding.
EDIT: read @v1vendi comment below to see why apply
is useful.
/* Ultra lightweight Github REST Client */
function generateAPI(baseUrl, defaults = {}) {
const callable = () => {};
callable.url = baseUrl;
return new Proxy(callable, {
get({ url }, propKey) {
const method = propKey.toUpperCase();
if (["GET", "POST", "PUT", "DELETE", "PATCH"].includes(method)) {
return (data, overrides = {}) => {
const payload = { method, ...defaults, ...overrides };
switch (method) {
case "GET": {
if (data) url = `${url}?${new URLSearchParams(data)}`;
break;
}
case "POST":
case "PUT":
case "PATCH": {
payload.body = JSON.stringify(data);
}
}
console.log(`Calling: ${url}`);
console.log("payload", payload);
return fetch(url, payload).then((d) => d.json());
};
}
return generateAPI(`${url}/${propKey}`, defaults);
},
});
}
const token = "github-token-here";
const githubClient = generateAPI("https://api.github.com", {
headers: {
"User-Agent": "xyz",
Authorization: `bearer ${token}`,
},
});
async function getRepo() {
/* GET /repos/{owner}/{repo} */
return githubClient.repos.davidwells.analytics.get();
}
async function generateRepoFromTemplate({ template, repoName }) {
/* POST /repos/{template_owner}/{template_repo}/generate */
return githubClient.repos[`${template}`].generate.post({ name: repoName });
}
getRepo().then((repoInfo) => {
console.log("repo", repoInfo);
});
@manuganji apply
was needed in my original concept (https://gist.github.com/v1vendi/75d5e5dad7a2d1ef3fcb48234e4528cb) so you could make it even more readable, as:
async function generateRepoFromTemplate({ template, repoName }) {
return githubClient.repos(template).generate.post({ name: repoName }); // instead of repos[`${template}`]
}
@v1vendi: Thank you! Ok, so all function calls will go to the apply
method? Your comment puts apply in better context and yes, makes this more readable.
@manuganji apply
in this context is a wrapper for Proxied original function. Basically we need the callable
variable only to use apply
on it. If we don't use this feature and stick to githubClient.repos[template].generate.post({ name: repoName })
, we could remove the callable
and do
return new Proxy({}, {
get(){ /*...* }
}
there's a doc for that
https://developer.mozilla.org/ru/docs/Web/JavaScript/Reference/Global_Objects/Proxy/Proxy/apply
Any chance that there is a feasible way to create a typed version of this in TypeScript?
Any chance that there is a feasible way to create a typed version of this in TypeScript?
@htunnicliff @About7Deaths @v1vendi I've updated my typed version of this REST API approach uncreate to support all the chaining just like in this example. 🚀
Nice work. I've made something similar, but more generic at https://github.com/SimplyEdit/simplyview/blob/master/js/simply.api.js
Does anyone know of any other similar approaches using Proxy?
Amazing work
This is pretty cool. Also, very similar to something I have been using for a while.
import {objectCopy} from "./objectCopy.js"
const defaults = {
cache: "no-cache",
credentials: "same-origin",
headers: {
"Content-Type": "application/json",
},
mode: "same-origin",
redirect: "follow",
referrerPolicy: "no-referrer"
}
const METHODS = [
"DELETE",
"GET",
"HEAD",
"OPTIONS",
"PATCH",
"POST",
"PUT",
]
function naiveSDK (root, config = {}, fetchAPI = fetch) {
if (!fetchAPI) {
throw new Error("No fetch API available.")
}
const defaultOptions = {...objectCopy(defaults), ...objectCopy(config)}
const request = (method) => (route, body, options = {}) => fetchAPI(
`${root}${route}`,
{
...(body ? {body: JSON.stringify(body)} : {}),
method,
options: {...defaultOptions, ...objectCopy(options)},
},
)
return new Proxy(METHODS, {
get: (all, method) => all.includes(method.toUpperCase())
? request(method.toUpperCase())
: () => {throw new Error(`Invalid HTTP method called: ${method}.`)},
set (_, prop) {throw new Error(`Attempting to set property "${prop}".`)},
})
}
export {naiveSDK}
@DavidWells This is an impressive example of the power of proxies. Thanks for sharing.
generateRepoFromTemplate
I assume would be used to generate new repos with therepoInfo
data obtained from callinggetRepo
correct? It doesn't seem like it has been utilized in this exampel.