kitcn
Version:
kitcn - React Query integration and CLI tools for Convex
117 lines (116 loc) • 4.07 kB
JavaScript
//#region src/shared/meta-utils.ts
const metaCache = /* @__PURE__ */ new WeakMap();
const nonMetaLeafKeys = new Set(["functionRef", "ref"]);
function isRecord(value) {
return typeof value === "object" && value !== null;
}
function isFunctionType(value) {
return value === "query" || value === "mutation" || value === "action";
}
function isMetaScalar(value) {
return typeof value === "string" || typeof value === "number" || typeof value === "boolean";
}
function extractLeafMeta(value) {
const type = value.type;
if (!isFunctionType(type)) return;
const result = { type };
for (const [key, entry] of Object.entries(value)) {
if (key === "type" || nonMetaLeafKeys.has(key) || key.startsWith("_")) continue;
if (entry === void 0) continue;
if (isMetaScalar(entry)) result[key] = entry;
}
return result;
}
function getHttpRoutes(api) {
const routes = api._http;
if (!isRecord(routes)) return;
const normalized = {};
for (const [routeKey, routeValue] of Object.entries(routes)) {
if (!isRecord(routeValue)) continue;
const routePath = routeValue.path;
const routeMethod = routeValue.method;
if (typeof routePath === "string" && typeof routeMethod === "string") normalized[routeKey] = {
path: routePath,
method: routeMethod
};
}
return normalized;
}
/**
* Build a metadata index from merged API leaves.
* Supports both generated `api` objects and plain metadata fixtures.
*/
function buildMetaIndex(api) {
const cached = metaCache.get(api);
if (cached) return cached;
const meta = {};
const httpRoutes = getHttpRoutes(api);
if (httpRoutes) meta._http = httpRoutes;
const walk = (node, path) => {
for (const [key, value] of Object.entries(node)) {
if (key.startsWith("_")) continue;
if (!isRecord(value)) continue;
const leafMeta = extractLeafMeta(value);
if (leafMeta) {
if (path.length === 0) continue;
const namespace = path.join("/");
meta[namespace] ??= {};
meta[namespace][key] = leafMeta;
continue;
}
walk(value, [...path, key]);
}
};
walk(api, []);
metaCache.set(api, meta);
return meta;
}
/**
* Get a function reference from the API object by traversing the path.
*/
function getFuncRef(api, path) {
let current = api;
for (const key of path) if (current && typeof current === "object") {
const next = current[key];
if (next === void 0) throw new Error(`Invalid path: ${path.join(".")}`);
current = next;
} else throw new Error(`Invalid path: ${path.join(".")}`);
if (current && typeof current === "object") {
const maybeFunctionRef = current.functionRef;
if (maybeFunctionRef && typeof maybeFunctionRef === "object") return maybeFunctionRef;
}
if (!current || typeof current !== "object") throw new Error(`Invalid function reference at path: ${path.join(".")}`);
return current;
}
/**
* Get function type from meta using path.
* Supports nested paths like ['items', 'queries', 'list'] → namespace='items/queries', fn='list'
*
* @param path - Path segments like ['todos', 'create'] or ['items', 'queries', 'list']
* @param meta - The meta object from codegen
* @returns Function type or 'query' as default
*/
function getFunctionType(path, source) {
if (path.length < 2) return "query";
const meta = buildMetaIndex(source);
const fnName = path.at(-1);
const fnType = meta[path.slice(0, -1).join("/")]?.[fnName]?.type;
if (fnType === "query" || fnType === "mutation" || fnType === "action") return fnType;
return "query";
}
/**
* Get function metadata from meta using path.
* Supports nested paths like ['items', 'queries', 'list'] → namespace='items/queries', fn='list'
*
* @param path - Path segments like ['todos', 'create'] or ['items', 'queries', 'list']
* @param meta - The meta object from codegen
* @returns Function metadata or undefined
*/
function getFunctionMeta(path, source) {
if (path.length < 2) return;
const meta = buildMetaIndex(source);
const fnName = path.at(-1);
return meta[path.slice(0, -1).join("/")]?.[fnName];
}
//#endregion
export { getFunctionType as i, getFuncRef as n, getFunctionMeta as r, buildMetaIndex as t };