Fix critical issues C1-C5, C7, H1, H2, H4, H10
C1+C7: Cache purge now passes user_id and works for view-path mutations. Extracted _purge_cache_for_invalidation() shared helper used by both RPC and view-path branches. C2: initSession retries 3x with backoff. Resets on total failure so next call tries again instead of permanently broken CSRF. C3: SSR template backend injects __MIZAN_SSR_DATA__ script tag with serialized props for client-side hydration. C4: SSR bridge uses _write_lock to serialize stdin writes from concurrent Django threads. Prevents JSON interleaving. C5: SSR bridge registers atexit handler for process cleanup. No more orphaned Bun processes on Django reload/shutdown. H1: pendingScoped changed from Map to Array — multiple scoped invalidations for the same context no longer overwrite. H2: registerContext uses stableKey() (sorted JSON) instead of bare JSON.stringify. Property order no longer matters. H4: Named context providers skip refetch if SSR data exists (matches global context behavior). H10: _meta always assigned as fresh dict, preventing shared-dict mutation across ServerFunction subclasses. 373 Django + 33 React tests pass. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -58,19 +58,26 @@ let _sessionReady: Promise<void> | null = null
|
||||
* Initialize a session (fetches CSRF cookie from GET /session/).
|
||||
* Called automatically on first fetch if not called explicitly.
|
||||
* No-op if a CSRF cookie already exists.
|
||||
* Retries on failure — resets so next call tries again.
|
||||
*/
|
||||
export function initSession(): Promise<void> {
|
||||
if (_sessionReady) return _sessionReady
|
||||
|
||||
_sessionReady = (async () => {
|
||||
// If we already have a CSRF token, skip
|
||||
if (getCSRFToken()) return
|
||||
|
||||
try {
|
||||
await fetch(`${config.baseUrl}/session/`, { credentials: 'include' })
|
||||
} catch (e) {
|
||||
console.error('[mizan] Session init failed:', e)
|
||||
for (let attempt = 0; attempt < 3; attempt++) {
|
||||
try {
|
||||
await fetch(`${config.baseUrl}/session/`, { credentials: 'include' })
|
||||
if (getCSRFToken()) return
|
||||
} catch (e) {
|
||||
console.warn(`[mizan] Session init attempt ${attempt + 1} failed:`, e)
|
||||
}
|
||||
if (attempt < 2) await new Promise(r => setTimeout(r, (attempt + 1) * 100))
|
||||
}
|
||||
|
||||
// All retries failed — reset so next call tries again
|
||||
_sessionReady = null
|
||||
})()
|
||||
|
||||
return _sessionReady
|
||||
@@ -88,26 +95,31 @@ interface ContextEntry {
|
||||
|
||||
const contexts: Map<string, Map<ParamKey, ContextEntry>> = new Map()
|
||||
|
||||
/** Deterministic JSON key for params — sorted to avoid order-dependency */
|
||||
function stableKey(params: Record<string, any>): string {
|
||||
return JSON.stringify(params, Object.keys(params).sort())
|
||||
}
|
||||
|
||||
export function registerContext(
|
||||
name: string,
|
||||
params: Record<string, any>,
|
||||
refetch: RefetchFn,
|
||||
): () => void {
|
||||
if (!contexts.has(name)) contexts.set(name, new Map())
|
||||
const key = JSON.stringify(params)
|
||||
const key = stableKey(params)
|
||||
contexts.get(name)!.set(key, { params, refetch })
|
||||
return () => contexts.get(name)!.delete(key)
|
||||
return () => contexts.get(name)?.delete(key)
|
||||
}
|
||||
|
||||
// === Invalidation ===
|
||||
|
||||
const pending: Set<string> = new Set()
|
||||
const pendingScoped: Map<string, Record<string, any>> = new Map()
|
||||
const pendingScoped: Array<{ context: string; params: Record<string, any> }> = []
|
||||
let scheduled = false
|
||||
|
||||
export function invalidate(context: string, params?: Record<string, any>): void {
|
||||
if (params) {
|
||||
pendingScoped.set(context, params)
|
||||
pendingScoped.push({ context, params })
|
||||
} else {
|
||||
pending.add(context)
|
||||
}
|
||||
@@ -123,17 +135,17 @@ function flush(): void {
|
||||
if (entries) entries.forEach(entry => entry.refetch())
|
||||
}
|
||||
|
||||
for (const [name, params] of pendingScoped) {
|
||||
for (const { context: name, params } of pendingScoped) {
|
||||
if (pending.has(name)) continue
|
||||
const entries = contexts.get(name)
|
||||
if (!entries) continue
|
||||
const key = JSON.stringify(params)
|
||||
const key = stableKey(params)
|
||||
const entry = entries.get(key)
|
||||
if (entry) entry.refetch()
|
||||
}
|
||||
|
||||
pending.clear()
|
||||
pendingScoped.clear()
|
||||
pendingScoped.length = 0
|
||||
scheduled = false
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user