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:
2026-04-07 03:08:32 -04:00
parent c237a6379b
commit 711e92ac4d
3 changed files with 49 additions and 15 deletions

View File

@@ -528,8 +528,8 @@ import { DjangoError, type FunctionErrorResponse } from '../errors'
* Success response from a server function
*/
export interface FunctionSuccessResponse<T> {
error: false
data: T
result: T
invalidate?: Array<string | { context: string; params: Record<string, any> }>
}
/**
@@ -580,6 +580,6 @@ export async function httpFunctionCall<TInput = unknown, TOutput = unknown>(
throw new DjangoError(data)
}
return data.data
return (data as FunctionSuccessResponse<TOutput>).result
}

View File

@@ -343,13 +343,26 @@ export function MizanProvider({
{ fn: functionName, args: input }
)
const data: FunctionResponse<TOutput> = 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]
)