diff --git a/packages/mizan-django/generate/generator/lib/mizan.mjs b/packages/mizan-django/generate/generator/lib/mizan.mjs index a647797..f8b455b 100644 --- a/packages/mizan-django/generate/generator/lib/mizan.mjs +++ b/packages/mizan-django/generate/generator/lib/mizan.mjs @@ -268,15 +268,22 @@ export function generateMizanProvider(schema, options = {}) { lines.push(' if (loaded.current) return') lines.push(' loaded.current = true') lines.push('') + lines.push(' // Check for SSR hydration data first') + lines.push(" const ssr = typeof window !== 'undefined' && (window as any).__MIZAN_SSR_DATA__") + lines.push(' if (ssr) {') + lines.push(' for (const [name, data] of Object.entries(ssr)) {') + lines.push(' mizan.setContextData(name, data)') + lines.push(' }') + lines.push(' return') + lines.push(' }') + lines.push('') lines.push(' ;(async () => {') lines.push(' await mizan.whenReady') lines.push(' try {') lines.push(" const response = await mizan.request('GET', `${mizan.baseUrl}/ctx/global/`)") lines.push(' const result = await response.json()') - lines.push(' if (!result.error) {') - lines.push(' for (const [name, data] of Object.entries(result.data)) {') - lines.push(' mizan.setContextData(name, data)') - lines.push(' }') + lines.push(' for (const [name, data] of Object.entries(result)) {') + lines.push(' mizan.setContextData(name, data)') lines.push(' }') lines.push(' } catch (e) {') lines.push(" console.error('[MizanContext] Global context fetch failed:', e)") @@ -456,11 +463,25 @@ export function generateMizanProvider(schema, options = {}) { // Provider component lines.push(`export function ${ctxPascal}Context({ children, ...params }: ${ctxPascal}ContextProps) {`) lines.push(` const mizan = useMizan()`) + + // SSR hydration check — initialize from __MIZAN_SSR_DATA__ if available lines.push(` const [data, setData] = useState<{`) for (const fn of ctxFunctions) { lines.push(` ${fn.name}: ${fn.outputType}`) } - lines.push(` } | null>(null)`) + lines.push(` } | null>(() => {`) + lines.push(` if (typeof window === 'undefined') return null`) + lines.push(` const ssr = (window as any).__MIZAN_SSR_DATA__`) + lines.push(` if (!ssr) return null`) + // Check if all functions for this context have SSR data + const firstFn = ctxFunctions[0] + lines.push(` if (ssr.${firstFn.name} === undefined) return null`) + lines.push(` return {`) + for (const fn of ctxFunctions) { + lines.push(` ${fn.name}: ssr.${fn.name},`) + } + lines.push(` }`) + lines.push(` })`) lines.push('') lines.push(` const refetch = useCallback(async () => {`) lines.push(` await mizan.whenReady`) @@ -470,7 +491,7 @@ export function generateMizanProvider(schema, options = {}) { } lines.push(` const resp = await mizan.request('GET', \`\${mizan.baseUrl}/ctx/${ctxName}/?\${qs}\`)`) lines.push(` const result = await resp.json()`) - lines.push(` if (!result.error) setData(result.data)`) + lines.push(` setData(result)`) // Dependency array: mizan + each param const deps = ['mizan', ...paramEntries.map(([pName]) => `params.${pName}`)] @@ -650,13 +671,13 @@ export function generateMizanServer(schema) { lines.push('') lines.push(' try {') lines.push(" const response = await client.request('GET', '/api/mizan/ctx/global/')") - lines.push(' const result = await response.json()') - lines.push(' if (!result.error) {') + lines.push(' if (response.ok) {') + lines.push(' const result = await response.json()') for (const ctx of globalContexts) { - lines.push(` if (result.data?.${ctx.name} !== undefined) hydration.${ctx.camelName} = result.data.${ctx.name}`) + lines.push(` if (result?.${ctx.name} !== undefined) hydration.${ctx.camelName} = result.${ctx.name}`) } lines.push(' } else {') - lines.push(" console.error('[getMizanHydration] Global context fetch failed:', result.code, result.message)") + lines.push(" console.error('[getMizanHydration] Global context fetch failed:', response.status)") lines.push(' }') lines.push(' } catch (e) {') lines.push(" console.error('[getMizanHydration] Request failed:', e)") diff --git a/packages/mizan-react/src/client/index.ts b/packages/mizan-react/src/client/index.ts index 4b0d79f..89fe2a4 100644 --- a/packages/mizan-react/src/client/index.ts +++ b/packages/mizan-react/src/client/index.ts @@ -528,8 +528,8 @@ import { DjangoError, type FunctionErrorResponse } from '../errors' * Success response from a server function */ export interface FunctionSuccessResponse { - error: false - data: T + result: T + invalidate?: Array }> } /** @@ -580,6 +580,6 @@ export async function httpFunctionCall( throw new DjangoError(data) } - return data.data + return (data as FunctionSuccessResponse).result } diff --git a/packages/mizan-react/src/context.tsx b/packages/mizan-react/src/context.tsx index d2d961f..d4a6fa1 100644 --- a/packages/mizan-react/src/context.tsx +++ b/packages/mizan-react/src/context.tsx @@ -343,13 +343,26 @@ export function MizanProvider({ { fn: functionName, args: input } ) - const data: FunctionResponse = await response.json() + const data = await response.json() if (data.error) { throw new DjangoError(data as FunctionErrorResponse) } - return data.data + // Server-driven invalidation: process the invalidate array + if (data.invalidate && Array.isArray(data.invalidate)) { + for (const entry of data.invalidate) { + if (typeof entry === 'string') { + const provider = contextProvidersRef.current.get(entry) + if (provider) provider.refetch() + } else if (entry.context) { + const provider = contextProvidersRef.current.get(entry.context) + if (provider) provider.refetch() + } + } + } + + return data.result as TOutput }, [connection, baseUrl, httpClient] )