refactor(sdk): split hooks, dsr-portal, provider, sync approaching 500 LOC

All four files split into focused sibling modules so every file lands
comfortably under the 300-LOC soft target (hard cap 500):

  hooks.ts (474→43)  → hooks-core / hooks-dsgvo / hooks-compliance
                        hooks-rag-security / hooks-ui
  dsr-portal.ts (464→129) → dsr-portal-translations / dsr-portal-render
  provider.tsx (462→247)  → provider-effects / provider-callbacks
  sync.ts (435→299)       → sync-storage / sync-conflict

Zero behaviour changes. All public APIs remain importable from the
original paths (hooks.ts re-exports every hook, provider.tsx keeps all
named exports, sync.ts preserves StateSyncManager + factory).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Sharang Parnerkar
2026-04-18 08:40:20 +02:00
parent 19d6437161
commit 9ecd3b2d84
15 changed files with 1700 additions and 1299 deletions

View File

@@ -0,0 +1,226 @@
'use client'
/**
* useCallback factories extracted from ComplianceProvider.
*
* Each function creates and returns a memoised callback so provider.tsx
* stays under 300 LOC.
*/
import { useCallback, RefObject } from 'react'
import type {
SDKState,
CheckpointStatus,
UseCaseAssessment,
Risk,
Control,
} from '@breakpilot/compliance-sdk-types'
import { ComplianceClient, StateSyncManager } from '@breakpilot/compliance-sdk-core'
import { SDK_STORAGE_KEY } from './provider-context'
// =============================================================================
// CHECKPOINT CALLBACKS
// =============================================================================
export function useValidateCheckpoint(
state: SDKState,
enableBackendSync: boolean,
client: ComplianceClient,
dispatch: React.Dispatch<{ type: string; payload?: unknown }>
) {
return useCallback(
async (checkpointId: string): Promise<CheckpointStatus> => {
if (enableBackendSync) {
try {
const result = await client.validateCheckpoint(checkpointId, state)
const status: CheckpointStatus = {
checkpointId: result.checkpointId,
passed: result.passed,
validatedAt: new Date(result.validatedAt),
validatedBy: result.validatedBy,
errors: result.errors,
warnings: result.warnings,
}
dispatch({ type: 'SET_CHECKPOINT_STATUS', payload: { id: checkpointId, status } })
return status
} catch {
// Fall through to local validation
}
}
const status: CheckpointStatus = {
checkpointId,
passed: true,
validatedAt: new Date(),
validatedBy: 'SYSTEM',
errors: [],
warnings: [],
}
dispatch({ type: 'SET_CHECKPOINT_STATUS', payload: { id: checkpointId, status } })
return status
},
[state, enableBackendSync, client, dispatch]
)
}
export function useOverrideCheckpoint(
state: SDKState,
dispatch: React.Dispatch<{ type: string; payload?: unknown }>
) {
return useCallback(
async (checkpointId: string, reason: string): Promise<void> => {
const existingStatus = state.checkpoints[checkpointId]
const overriddenStatus: CheckpointStatus = {
...existingStatus,
checkpointId,
passed: true,
overrideReason: reason,
overriddenBy: state.userId,
overriddenAt: new Date(),
errors: [],
warnings: existingStatus?.warnings || [],
}
dispatch({ type: 'SET_CHECKPOINT_STATUS', payload: { id: checkpointId, status: overriddenStatus } })
},
[state.checkpoints, state.userId, dispatch]
)
}
export function useGetCheckpointStatus(state: SDKState) {
return useCallback(
(checkpointId: string): CheckpointStatus | undefined => state.checkpoints[checkpointId],
[state.checkpoints]
)
}
// =============================================================================
// STATE UPDATE CALLBACKS
// =============================================================================
export function useUpdateUseCase(dispatch: React.Dispatch<{ type: string; payload?: unknown }>) {
return useCallback(
(id: string, data: Partial<UseCaseAssessment>) => {
dispatch({ type: 'UPDATE_USE_CASE', payload: { id, data } })
},
[dispatch]
)
}
export function useAddRisk(dispatch: React.Dispatch<{ type: string; payload?: unknown }>) {
return useCallback(
(risk: Risk) => {
dispatch({ type: 'ADD_RISK', payload: risk })
},
[dispatch]
)
}
export function useUpdateControl(dispatch: React.Dispatch<{ type: string; payload?: unknown }>) {
return useCallback(
(id: string, data: Partial<Control>) => {
dispatch({ type: 'UPDATE_CONTROL', payload: { id, data } })
},
[dispatch]
)
}
// =============================================================================
// PERSISTENCE CALLBACKS
// =============================================================================
export function useSaveState(
state: SDKState,
tenantId: string,
enableBackendSync: boolean,
syncManagerRef: RefObject<StateSyncManager | null>,
setError: (e: Error) => void
) {
return useCallback(async (): Promise<void> => {
try {
if (typeof window !== 'undefined') {
localStorage.setItem(`${SDK_STORAGE_KEY}-${tenantId}`, JSON.stringify(state))
}
if (enableBackendSync && syncManagerRef.current) {
await syncManagerRef.current.forceSync(state)
}
} catch (err) {
setError(err as Error)
throw err
}
}, [state, tenantId, enableBackendSync, syncManagerRef, setError])
}
export function useLoadState(
tenantId: string,
enableBackendSync: boolean,
syncManagerRef: RefObject<StateSyncManager | null>,
setIsLoading: (v: boolean) => void,
dispatch: React.Dispatch<{ type: string; payload?: unknown }>,
setError: (e: Error) => void
) {
return useCallback(async (): Promise<void> => {
setIsLoading(true)
try {
if (enableBackendSync && syncManagerRef.current) {
const serverState = await syncManagerRef.current.loadFromServer()
if (serverState) {
dispatch({ type: 'SET_STATE', payload: serverState })
return
}
}
if (typeof window !== 'undefined') {
const stored = localStorage.getItem(`${SDK_STORAGE_KEY}-${tenantId}`)
if (stored) {
dispatch({ type: 'SET_STATE', payload: JSON.parse(stored) })
}
}
} catch (err) {
setError(err as Error)
throw err
} finally {
setIsLoading(false)
}
}, [tenantId, enableBackendSync, syncManagerRef, setIsLoading, dispatch, setError])
}
export function useResetState(
tenantId: string,
dispatch: React.Dispatch<{ type: string; payload?: unknown }>
) {
return useCallback(() => {
dispatch({ type: 'RESET_STATE' })
if (typeof window !== 'undefined') {
localStorage.removeItem(`${SDK_STORAGE_KEY}-${tenantId}`)
}
}, [tenantId, dispatch])
}
// =============================================================================
// SYNC & EXPORT CALLBACKS
// =============================================================================
export function useForceSyncToServer(
state: SDKState,
enableBackendSync: boolean,
syncManagerRef: RefObject<StateSyncManager | null>
) {
return useCallback(async (): Promise<void> => {
if (enableBackendSync && syncManagerRef.current) {
await syncManagerRef.current.forceSync(state)
}
}, [state, enableBackendSync, syncManagerRef])
}
export function useExportState(state: SDKState, client: ComplianceClient) {
return useCallback(
async (format: 'json' | 'pdf' | 'zip'): Promise<Blob> => {
if (format === 'json') {
return new Blob([JSON.stringify(state, null, 2)], { type: 'application/json' })
}
return client.exportState(format)
},
[state, client]
)
}