/** * Vue Stage 2 — Generates composables from Stage 1 output. * * Subscribes to the kernel for state. Vue reactivity wraps kernel notifications. */ function pascalCase(str) { return str.split(/[.\-_]/).map(p => p.charAt(0).toUpperCase() + p.slice(1)).join('') } export function generateVueAdapter(schema) { const functions = schema['x-mizan-functions'] || [] const contextGroups = schema['x-mizan-contexts'] || {} const mutations = functions.filter(fn => !fn.isContext && !fn.isForm && fn.affects) const plainFns = functions.filter(fn => !fn.isContext && !fn.isForm && !fn.affects) const lines = [ '// AUTO-GENERATED by mizan — do not edit', '', "import { ref, computed, onMounted, onUnmounted, onServerPrefetch, type ComputedRef } from 'vue'", "import { registerContext, type ContextState } from '@mizan/runtime'", '', ] const stage1Imports = [] for (const [ctxName] of Object.entries(contextGroups)) { const p = pascalCase(ctxName) stage1Imports.push(`fetch${p}Context`, `type ${p}ContextData`, `type ${p}ContextParams`) } for (const fn of [...mutations, ...plainFns]) { stage1Imports.push(`call${pascalCase(fn.camelName)}`) } if (stage1Imports.length > 0) { lines.push(`import { ${stage1Imports.join(', ')} } from '../index'`) lines.push('') } for (const [ctxName, ctxMeta] of Object.entries(contextGroups)) { const p = pascalCase(ctxName) const ctxFunctions = functions.filter(fn => fn.isContext === ctxName) const paramEntries = Object.entries(ctxMeta.params || {}) const paramsArg = paramEntries.length > 0 ? 'params' : '{} as any' if (paramEntries.length > 0) { lines.push(`export function use${p}Context(params: ${p}ContextParams) {`) } else { lines.push(`export function use${p}Context() {`) } lines.push(` const state = ref>({ data: null, status: 'idle', error: null })`) lines.push(` let handle: ReturnType | null = null`) lines.push('') lines.push(` onMounted(() => {`) lines.push(` handle = registerContext('${ctxName}', ${paramsArg}, () => fetch${p}Context(${paramsArg}))`) lines.push(` handle.subscribe(() => { state.value = handle!.getState() })`) lines.push(` handle.refetch()`) lines.push(` })`) lines.push('') lines.push(` onServerPrefetch(async () => {`) lines.push(` handle = registerContext('${ctxName}', ${paramsArg}, () => fetch${p}Context(${paramsArg}))`) lines.push(` await handle.refetch()`) lines.push(` state.value = handle.getState()`) lines.push(` })`) lines.push('') lines.push(` onUnmounted(() => { handle?.unregister() })`) lines.push('') lines.push(` return {`) lines.push(` state,`) for (const fn of ctxFunctions) { lines.push(` ${fn.camelName}: computed(() => state.value.data?.${fn.name} ?? null) as ComputedRef<${fn.outputType} | null>,`) } lines.push(` loading: computed(() => state.value.status === 'loading'),`) lines.push(` error: computed(() => state.value.error),`) lines.push(` }`) lines.push('}') lines.push('') } for (const fn of [...mutations, ...plainFns]) { const p = pascalCase(fn.camelName) lines.push(`export function use${p}() {`) lines.push(` const isPending = ref(false)`) lines.push(` const error = ref(null)`) if (fn.hasInput) { lines.push(` async function mutate(args: Parameters[0]) {`) } else { lines.push(` async function mutate() {`) } lines.push(` isPending.value = true; error.value = null`) lines.push(` try { return await call${p}(${fn.hasInput ? 'args' : ''}) }`) lines.push(` catch (e) { error.value = e as Error; throw e }`) lines.push(` finally { isPending.value = false }`) lines.push(` }`) lines.push(` return { mutate, isPending, error }`) lines.push('}') lines.push('') } lines.push("export type { ContextState } from '@mizan/runtime'") lines.push("export { configure, initSession, MizanError } from '@mizan/runtime'") lines.push('') return lines.join('\n') }