/** * Edge Manifest Generator * * Produces the same JSON format as mizan-django. One Edge Worker. * Two backend languages. Same manifest. */ import type { EdgeManifest } from './types' import { getAllFunctions, getContextGroups, getContextParamNames } from './registry' // Both camelCase and snake_case forms included for cross-language matching. // Wire format is snake_case (protocol rule); camelCase is the TS-local convention. const USER_SCOPED_PARAMS = new Set(['userId', 'user', 'ownerId', 'accountId', 'user_id', 'owner_id', 'account_id']) export function generateManifest(baseUrl = '/api/mizan'): EdgeManifest { const groups = getContextGroups() const allFunctions = getAllFunctions() const manifest: EdgeManifest = { version: 1, contexts: {}, mutations: {} } // Contexts for (const [ctxName, fnNames] of Object.entries(groups)) { const paramNames = new Set() const functions: Array<{ name: string; path: 'rpc' | 'view'; route?: string; methods?: string[] }> = [] const pageRoutes: string[] = [] for (const fnName of fnNames) { const entry = allFunctions.get(fnName) if (!entry) continue for (const p of entry.params) paramNames.add(p.name) const fnEntry: any = { name: fnName, path: entry.viewPath ? 'view' : 'rpc' } if (entry.route) { fnEntry.route = entry.route fnEntry.methods = entry.methods || ['GET'] pageRoutes.push(entry.route) } if (entry.rev !== undefined && entry.rev !== 0) fnEntry.rev = entry.rev if (entry.cache !== undefined && entry.cache !== true) fnEntry.cache = entry.cache functions.push(fnEntry) } const sortedParams = [...paramNames].sort() const userScoped = [...paramNames].some(p => USER_SCOPED_PARAMS.has(p)) const ctxEntry: EdgeManifest['contexts'][string] = { functions, endpoints: [`${baseUrl}/ctx/${ctxName}/`], params: sortedParams, user_scoped: userScoped, render_strategy: userScoped ? 'dynamic_cached' : 'psr', } if (pageRoutes.length > 0) { ctxEntry.page_routes = pageRoutes } manifest.contexts[ctxName] = ctxEntry } // Mutations for (const [fnName, entry] of allFunctions) { if (!entry.affects) continue const affectedContexts = [...new Set(entry.affects.map(a => a.name))] // Auto-scoped params const fnParamNames = new Set(entry.params.map(p => p.name)) const autoScoped: string[] = [] for (const ctxName of affectedContexts) { const ctxParams = getContextParamNames(ctxName) for (const p of fnParamNames) { if (ctxParams.has(p) && !autoScoped.includes(p)) { autoScoped.push(p) } } } const mutation: EdgeManifest['mutations'][string] = { affects: affectedContexts, } if (autoScoped.length > 0) mutation.auto_scoped_params = autoScoped.sort() if (entry.private) mutation.private = true if (entry.route) { mutation.route = entry.route mutation.methods = entry.methods || ['POST'] } manifest.mutations[fnName] = mutation } return manifest }