UNPKG

kitcn

Version:

kitcn - React Query integration and CLI tools for Convex

117 lines (116 loc) 4.07 kB
//#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 };