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>
146 lines
4.6 KiB
TypeScript
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
|
|
}
|
|
}
|