Files
breakpilot-compliance/admin-compliance/lib/sdk/context-sync-helpers.ts
Sharang Parnerkar 786bb409e4 refactor(admin): split lib/sdk/context.tsx (1280 LOC) into focused modules
Extract the monolithic SDK context provider into seven focused modules:
- context-types.ts (203 LOC): SDKContextValue interface, initialState, ExtendedSDKAction
- context-reducer.ts (353 LOC): sdkReducer with all action handlers
- context-provider.tsx (495 LOC): SDKProvider component + SDKContext
- context-hooks.ts (17 LOC): useSDK hook
- context-validators.ts (94 LOC): local checkpoint validation logic
- context-projects.ts (67 LOC): project management API helpers
- context-sync-helpers.ts (145 LOC): sync infrastructure init/cleanup/callbacks
- context.tsx (23 LOC): barrel re-export preserving existing import paths

All files under the 500-line hard cap. Build verified with `npx next build`.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-10 13:55:42 +02:00

146 lines
4.6 KiB
TypeScript

import React from 'react'
import { SDKState } from './types'
import { SDKApiClient, getSDKApiClient, resetSDKApiClient } from './api-client'
import { StateSyncManager, createStateSyncManager, SyncState, SyncCallbacks } from './sync'
import { ExtendedSDKAction } from './context-types'
// =============================================================================
// SYNC CALLBACK BUILDER
// =============================================================================
/**
* Builds the SyncCallbacks object used by the StateSyncManager.
* Keeps the provider component cleaner by extracting this factory.
*/
export function buildSyncCallbacks(
setSyncState: React.Dispatch<React.SetStateAction<SyncState>>,
setIsOnline: React.Dispatch<React.SetStateAction<boolean>>,
dispatch: React.Dispatch<ExtendedSDKAction>,
stateRef: React.MutableRefObject<SDKState>
): SyncCallbacks {
return {
onSyncStart: () => {
setSyncState(prev => ({ ...prev, status: 'syncing' }))
},
onSyncComplete: (syncedState) => {
setSyncState(prev => ({
...prev,
status: 'idle',
lastSyncedAt: new Date(),
pendingChanges: 0,
}))
if (syncedState.lastModified > stateRef.current.lastModified) {
dispatch({ type: 'SET_STATE', payload: syncedState })
}
},
onSyncError: (error) => {
setSyncState(prev => ({
...prev,
status: 'error',
error: error.message,
}))
},
onConflict: () => {
setSyncState(prev => ({ ...prev, status: 'conflict' }))
},
onOffline: () => {
setIsOnline(false)
setSyncState(prev => ({ ...prev, status: 'offline' }))
},
onOnline: () => {
setIsOnline(true)
setSyncState(prev => ({ ...prev, status: 'idle' }))
},
}
}
// =============================================================================
// INITIAL STATE LOADER
// =============================================================================
/**
* Loads SDK state from localStorage and optionally from the server,
* dispatching SET_STATE as appropriate.
*/
export async function loadInitialState(params: {
storageKey: string
enableBackendSync: boolean
projectId?: string
syncManager: StateSyncManager | null
apiClient: SDKApiClient | null
dispatch: React.Dispatch<ExtendedSDKAction>
}): Promise<void> {
const { storageKey, enableBackendSync, projectId, syncManager, apiClient, dispatch } = params
// First, try loading from localStorage
const stored = localStorage.getItem(storageKey)
if (stored) {
const parsed = JSON.parse(stored)
if (parsed.lastModified) {
parsed.lastModified = new Date(parsed.lastModified)
}
dispatch({ type: 'SET_STATE', payload: parsed })
}
// Then, try loading from server if backend sync is enabled
if (enableBackendSync && syncManager) {
const serverState = await syncManager.loadFromServer()
if (serverState) {
const localTime = stored ? new Date(JSON.parse(stored).lastModified).getTime() : 0
const serverTime = new Date(serverState.lastModified).getTime()
if (serverTime > localTime) {
dispatch({ type: 'SET_STATE', payload: serverState })
}
}
}
// Load project metadata (name, status, etc.) from backend
if (enableBackendSync && projectId && apiClient) {
try {
const info = await apiClient.getProject(projectId)
dispatch({ type: 'SET_STATE', payload: { projectInfo: info } })
} catch (err) {
console.warn('Failed to load project info:', err)
}
}
}
// =============================================================================
// INIT / CLEANUP HELPERS
// =============================================================================
export function initSyncInfra(
enableBackendSync: boolean,
tenantId: string,
projectId: string | undefined,
apiClientRef: React.MutableRefObject<SDKApiClient | null>,
syncManagerRef: React.MutableRefObject<StateSyncManager | null>,
callbacks: SyncCallbacks
): void {
if (!enableBackendSync || typeof window === 'undefined') return
apiClientRef.current = getSDKApiClient(tenantId, projectId)
syncManagerRef.current = createStateSyncManager(
apiClientRef.current,
tenantId,
{ debounceMs: 2000, maxRetries: 3 },
callbacks,
projectId
)
}
export function cleanupSyncInfra(
enableBackendSync: boolean,
syncManagerRef: React.MutableRefObject<StateSyncManager | null>,
apiClientRef: React.MutableRefObject<SDKApiClient | null>
): void {
if (syncManagerRef.current) {
syncManagerRef.current.destroy()
syncManagerRef.current = null
}
if (enableBackendSync) {
resetSDKApiClient()
apiClientRef.current = null
}
}