nej-react-utils/macros/useQuery.macro.cjs
2024-12-27 23:50:13 +01:00

135 lines
4.6 KiB
JavaScript

const { createMacro, MacroError } = require("babel-plugin-macros")
const { addNamed } = require("@babel/helper-module-imports")
const path = require("path")
const Babel = require("@babel/core")
// `createMacro` is simply a function that ensures your macro is only
// called in the context of a babel transpilation and will throw an
// error with a helpful message if someone does not have babel-plugin-macros
// configured correctly
module.exports = createMacro(useQuery);
//jsdoc
/**
* @param {Object} param0
* @param {import("@babel/types").ArrayExpression} param0.references
* @param {Object} param0.state
* @param {Babel} param0.babel
*/
function useQuery({ references, state, babel }) {
// state is the second argument you're passed to a visitor in a
// normal babel plugin. `babel` is the `babel-plugin-macros` module.
// do whatever you like to the AST paths you find in `references`
// read more below...
const { default: defaultImport = [] } = references
defaultImport.forEach(referencePath => {
if (referencePath.parentPath.type != "CallExpression") {
throw new MacroError(
"useQuery should be called with a function",
)
}
const ourArguments = referencePath.parentPath.get("arguments")
if (ourArguments.length < 1) {
throw new MacroError(
"There are not enough arguments",
)
}
else if (ourArguments.length > 1) {
throw new MacroError(
"There are too many arguments",
)
}
//jsdoc
/**
* @type {import("@babel/types").CallExpression}
* */
let call = ourArguments[0].node
let declPath = referencePath.parentPath.parentPath;
let setStateIdentifier = declPath.scope.generateUidIdentifier("setStateInternalMagic")
let refreshFuncName = declPath.scope.generateUidIdentifier("refreshInternalMagic")
let queryObjName = declPath.scope.generateUidIdentifier("queryInternalMagic");
let assignParsPath = referencePath.parentPath.parentPath.get("id");
let pars = assignParsPath.node
if (!assignParsPath.isObjectPattern()) {
if (!assignParsPath.isArrayPattern() || !assignParsPath.get("elements")[0].isObjectPattern())
throw new MacroError(
"Return is either just the object or array of object and refresh method",
)
pars = assignParsPath.get("elements")[0].node
if (assignParsPath.get("elements").length > 1) {
if (!assignParsPath.get("elements")[1].isIdentifier())
throw new MacroError(
"There is second argument to array return, but its surprisingly not a function identifier",
)
refreshFuncName = assignParsPath.get("elements")[1].node
}
}
let useStateHookImport = addNamed(declPath, "useState", "react");
let useCallbackHookImport = addNamed(declPath, "useCallback", "react");
let useEffectHookImport = addNamed(declPath, "useEffect", "react");
let useRQuery = addNamed(declPath, "useQuery", "@tanstack/react-query");
//create a string literal for identification
//reduce the callee's MemberExpression object and property to astring with member.member.member.etc...
var reduceCallee = (callee) => {
if (callee.type == "Identifier") return callee.name
return reduceCallee(callee.object) + "." + reduceCallee(callee.property)
}
let fullCallPathString = reduceCallee(call.callee)
let servicePath = "@services/api";
let apiErrorImport = addNamed(declPath, "ApiError", servicePath);
let useStateSrc = babel.template("[DATA, CALL_SET] = USE_STATE({data: null, error: null, loading: true})")({
DATA: pars,
CALL_SET: setStateIdentifier,
USE_STATE: useStateHookImport
})
declPath.replaceWith(useStateSrc)
let src = babel.template(`
const Q_UNAME = USE_RQUERY({queryKey: ["Q_KEY", ...(new Array(DEPS))], queryFn: async () => IMPORT_NAME})
USE_EFFECT(() => {
if(Q_UNAME.error instanceof ERROR_CLASS){
CALL_SET({...Q_UNAME, error: Q_UNAME.error.body, loading: Q_UNAME.isLoading})
return
}
CALL_SET({...Q_UNAME, loading: Q_UNAME.isLoading})
}, [Q_UNAME.data, Q_UNAME.error, Q_UNAME.isLoading])
const REFRESH = USE_CALLBACK(async () =>{
await Q_UNAME.refetch()
}, [Q_UNAME.data, Q_UNAME.error, Q_UNAME.isLoading])
`)({
IMPORT_NAME: call,
DEPS: call.arguments,
CALL_SET: setStateIdentifier,
USE_EFFECT: useEffectHookImport,
USE_CALLBACK: useCallbackHookImport,
ERROR_CLASS: apiErrorImport,
REFRESH: refreshFuncName,
Q_UNAME: queryObjName,
Q_KEY: fullCallPathString,
USE_RQUERY: useRQuery
})
declPath.parentPath.insertAfter(src)
})
}