Fix protocol mismatch + add SSR hydration to codegen
Three bugs fixed:
1. MizanProvider.call() read data.data but server returns data.result.
Now reads data.result and processes data.invalidate for server-driven
invalidation (triggering refetch on mounted context providers).
2. GlobalContextLoader expected {error, data} wrapper but context GET
returns raw bundled data. Fixed to iterate response directly.
3. Named context providers had same wrapper assumption. Fixed to
setData(result) directly.
Two features added:
1. SSR hydration: GlobalContextLoader checks window.__MIZAN_SSR_DATA__
on mount. If present, populates contexts from it and skips fetch.
2. SSR hydration: Named context providers check __MIZAN_SSR_DATA__ in
useState initializer. If SSR data exists for their functions, they
render immediately without fetching.
3. Server-driven invalidation in MizanProvider.call(): reads the
invalidate array from mutation responses and triggers refetch on
mounted providers. Generated mutation hooks' hardcoded invalidation
is now redundant but idempotent — both paths coexist safely.
Also fixed FunctionSuccessResponse type to match new protocol:
{ result: T, invalidate?: [...] }
373 Django + 33 React tests pass.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -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)")
|
||||
|
||||
Reference in New Issue
Block a user