Services: Admin-Compliance, Backend-Compliance, AI-Compliance-SDK, Consent-SDK, Developer-Portal, PCA-Platform, DSMS Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
415 lines
10 KiB
TypeScript
415 lines
10 KiB
TypeScript
/**
|
|
* SDK State Management
|
|
*
|
|
* Reducer-based state management with persistence support
|
|
*/
|
|
|
|
import type {
|
|
SDKState,
|
|
SDKAction,
|
|
UserPreferences,
|
|
SDKPhase,
|
|
SubscriptionTier,
|
|
getStepById,
|
|
} from '@breakpilot/compliance-sdk-types'
|
|
|
|
// =============================================================================
|
|
// INITIAL STATE
|
|
// =============================================================================
|
|
|
|
const initialPreferences: UserPreferences = {
|
|
language: 'de',
|
|
theme: 'light',
|
|
compactMode: false,
|
|
showHints: true,
|
|
autoSave: true,
|
|
autoValidate: true,
|
|
}
|
|
|
|
export const initialState: SDKState = {
|
|
// Metadata
|
|
version: '1.0.0',
|
|
lastModified: new Date(),
|
|
|
|
// Tenant & User
|
|
tenantId: '',
|
|
userId: '',
|
|
subscription: 'PROFESSIONAL' as SubscriptionTier,
|
|
|
|
// Progress
|
|
currentPhase: 1 as SDKPhase,
|
|
currentStep: 'use-case-workshop',
|
|
completedSteps: [],
|
|
checkpoints: {},
|
|
|
|
// Phase 1 Data
|
|
useCases: [],
|
|
activeUseCase: null,
|
|
screening: null,
|
|
modules: [],
|
|
requirements: [],
|
|
controls: [],
|
|
evidence: [],
|
|
checklist: [],
|
|
risks: [],
|
|
|
|
// Phase 2 Data
|
|
aiActClassification: null,
|
|
obligations: [],
|
|
dsfa: null,
|
|
toms: [],
|
|
retentionPolicies: [],
|
|
vvt: [],
|
|
documents: [],
|
|
cookieBanner: null,
|
|
consents: [],
|
|
dsrConfig: null,
|
|
dsrRequests: [],
|
|
escalationWorkflows: [],
|
|
|
|
// Security
|
|
sbom: null,
|
|
securityIssues: [],
|
|
securityBacklog: [],
|
|
|
|
// UI State
|
|
commandBarHistory: [],
|
|
recentSearches: [],
|
|
preferences: initialPreferences,
|
|
}
|
|
|
|
// =============================================================================
|
|
// REDUCER
|
|
// =============================================================================
|
|
|
|
export function sdkReducer(state: SDKState, action: SDKAction): SDKState {
|
|
const updateState = (updates: Partial<SDKState>): SDKState => ({
|
|
...state,
|
|
...updates,
|
|
lastModified: new Date(),
|
|
})
|
|
|
|
switch (action.type) {
|
|
case 'SET_STATE':
|
|
return updateState(action.payload)
|
|
|
|
case 'SET_CURRENT_STEP': {
|
|
// Import dynamically to avoid circular dependencies
|
|
const { getStepById } = require('@breakpilot/compliance-sdk-types')
|
|
const step = getStepById(action.payload)
|
|
return updateState({
|
|
currentStep: action.payload,
|
|
currentPhase: step?.phase || state.currentPhase,
|
|
})
|
|
}
|
|
|
|
case 'COMPLETE_STEP':
|
|
if (state.completedSteps.includes(action.payload)) {
|
|
return state
|
|
}
|
|
return updateState({
|
|
completedSteps: [...state.completedSteps, action.payload],
|
|
})
|
|
|
|
case 'SET_CHECKPOINT_STATUS':
|
|
return updateState({
|
|
checkpoints: {
|
|
...state.checkpoints,
|
|
[action.payload.id]: action.payload.status,
|
|
},
|
|
})
|
|
|
|
// Use Cases
|
|
case 'ADD_USE_CASE':
|
|
return updateState({
|
|
useCases: [...state.useCases, action.payload],
|
|
})
|
|
|
|
case 'UPDATE_USE_CASE':
|
|
return updateState({
|
|
useCases: state.useCases.map(uc =>
|
|
uc.id === action.payload.id ? { ...uc, ...action.payload.data } : uc
|
|
),
|
|
})
|
|
|
|
case 'DELETE_USE_CASE':
|
|
return updateState({
|
|
useCases: state.useCases.filter(uc => uc.id !== action.payload),
|
|
activeUseCase: state.activeUseCase === action.payload ? null : state.activeUseCase,
|
|
})
|
|
|
|
case 'SET_ACTIVE_USE_CASE':
|
|
return updateState({ activeUseCase: action.payload })
|
|
|
|
// Screening
|
|
case 'SET_SCREENING':
|
|
return updateState({ screening: action.payload })
|
|
|
|
// Modules
|
|
case 'ADD_MODULE':
|
|
return updateState({
|
|
modules: [...state.modules, action.payload],
|
|
})
|
|
|
|
case 'UPDATE_MODULE':
|
|
return updateState({
|
|
modules: state.modules.map(m =>
|
|
m.id === action.payload.id ? { ...m, ...action.payload.data } : m
|
|
),
|
|
})
|
|
|
|
// Requirements
|
|
case 'ADD_REQUIREMENT':
|
|
return updateState({
|
|
requirements: [...state.requirements, action.payload],
|
|
})
|
|
|
|
case 'UPDATE_REQUIREMENT':
|
|
return updateState({
|
|
requirements: state.requirements.map(r =>
|
|
r.id === action.payload.id ? { ...r, ...action.payload.data } : r
|
|
),
|
|
})
|
|
|
|
// Controls
|
|
case 'ADD_CONTROL':
|
|
return updateState({
|
|
controls: [...state.controls, action.payload],
|
|
})
|
|
|
|
case 'UPDATE_CONTROL':
|
|
return updateState({
|
|
controls: state.controls.map(c =>
|
|
c.id === action.payload.id ? { ...c, ...action.payload.data } : c
|
|
),
|
|
})
|
|
|
|
// Evidence
|
|
case 'ADD_EVIDENCE':
|
|
return updateState({
|
|
evidence: [...state.evidence, action.payload],
|
|
})
|
|
|
|
case 'UPDATE_EVIDENCE':
|
|
return updateState({
|
|
evidence: state.evidence.map(e =>
|
|
e.id === action.payload.id ? { ...e, ...action.payload.data } : e
|
|
),
|
|
})
|
|
|
|
case 'DELETE_EVIDENCE':
|
|
return updateState({
|
|
evidence: state.evidence.filter(e => e.id !== action.payload),
|
|
})
|
|
|
|
// Risks
|
|
case 'ADD_RISK':
|
|
return updateState({
|
|
risks: [...state.risks, action.payload],
|
|
})
|
|
|
|
case 'UPDATE_RISK':
|
|
return updateState({
|
|
risks: state.risks.map(r =>
|
|
r.id === action.payload.id ? { ...r, ...action.payload.data } : r
|
|
),
|
|
})
|
|
|
|
case 'DELETE_RISK':
|
|
return updateState({
|
|
risks: state.risks.filter(r => r.id !== action.payload),
|
|
})
|
|
|
|
// AI Act
|
|
case 'SET_AI_ACT_RESULT':
|
|
return updateState({ aiActClassification: action.payload })
|
|
|
|
// Obligations
|
|
case 'ADD_OBLIGATION':
|
|
return updateState({
|
|
obligations: [...state.obligations, action.payload],
|
|
})
|
|
|
|
case 'UPDATE_OBLIGATION':
|
|
return updateState({
|
|
obligations: state.obligations.map(o =>
|
|
o.id === action.payload.id ? { ...o, ...action.payload.data } : o
|
|
),
|
|
})
|
|
|
|
// DSFA
|
|
case 'SET_DSFA':
|
|
return updateState({ dsfa: action.payload })
|
|
|
|
// TOMs
|
|
case 'ADD_TOM':
|
|
return updateState({
|
|
toms: [...state.toms, action.payload],
|
|
})
|
|
|
|
case 'UPDATE_TOM':
|
|
return updateState({
|
|
toms: state.toms.map(t =>
|
|
t.id === action.payload.id ? { ...t, ...action.payload.data } : t
|
|
),
|
|
})
|
|
|
|
// Retention Policies
|
|
case 'ADD_RETENTION_POLICY':
|
|
return updateState({
|
|
retentionPolicies: [...state.retentionPolicies, action.payload],
|
|
})
|
|
|
|
case 'UPDATE_RETENTION_POLICY':
|
|
return updateState({
|
|
retentionPolicies: state.retentionPolicies.map(p =>
|
|
p.id === action.payload.id ? { ...p, ...action.payload.data } : p
|
|
),
|
|
})
|
|
|
|
// Processing Activities (VVT)
|
|
case 'ADD_PROCESSING_ACTIVITY':
|
|
return updateState({
|
|
vvt: [...state.vvt, action.payload],
|
|
})
|
|
|
|
case 'UPDATE_PROCESSING_ACTIVITY':
|
|
return updateState({
|
|
vvt: state.vvt.map(p =>
|
|
p.id === action.payload.id ? { ...p, ...action.payload.data } : p
|
|
),
|
|
})
|
|
|
|
// Documents
|
|
case 'ADD_DOCUMENT':
|
|
return updateState({
|
|
documents: [...state.documents, action.payload],
|
|
})
|
|
|
|
case 'UPDATE_DOCUMENT':
|
|
return updateState({
|
|
documents: state.documents.map(d =>
|
|
d.id === action.payload.id ? { ...d, ...action.payload.data } : d
|
|
),
|
|
})
|
|
|
|
// Cookie Banner
|
|
case 'SET_COOKIE_BANNER':
|
|
return updateState({ cookieBanner: action.payload })
|
|
|
|
// DSR
|
|
case 'SET_DSR_CONFIG':
|
|
return updateState({ dsrConfig: action.payload })
|
|
|
|
case 'ADD_DSR_REQUEST':
|
|
return updateState({
|
|
dsrRequests: [...state.dsrRequests, action.payload],
|
|
})
|
|
|
|
case 'UPDATE_DSR_REQUEST':
|
|
return updateState({
|
|
dsrRequests: state.dsrRequests.map(r =>
|
|
r.id === action.payload.id ? { ...r, ...action.payload.data } : r
|
|
),
|
|
})
|
|
|
|
// Escalation Workflows
|
|
case 'ADD_ESCALATION_WORKFLOW':
|
|
return updateState({
|
|
escalationWorkflows: [...state.escalationWorkflows, action.payload],
|
|
})
|
|
|
|
case 'UPDATE_ESCALATION_WORKFLOW':
|
|
return updateState({
|
|
escalationWorkflows: state.escalationWorkflows.map(w =>
|
|
w.id === action.payload.id ? { ...w, ...action.payload.data } : w
|
|
),
|
|
})
|
|
|
|
// Security
|
|
case 'ADD_SECURITY_ISSUE':
|
|
return updateState({
|
|
securityIssues: [...state.securityIssues, action.payload],
|
|
})
|
|
|
|
case 'UPDATE_SECURITY_ISSUE':
|
|
return updateState({
|
|
securityIssues: state.securityIssues.map(i =>
|
|
i.id === action.payload.id ? { ...i, ...action.payload.data } : i
|
|
),
|
|
})
|
|
|
|
case 'ADD_BACKLOG_ITEM':
|
|
return updateState({
|
|
securityBacklog: [...state.securityBacklog, action.payload],
|
|
})
|
|
|
|
case 'UPDATE_BACKLOG_ITEM':
|
|
return updateState({
|
|
securityBacklog: state.securityBacklog.map(i =>
|
|
i.id === action.payload.id ? { ...i, ...action.payload.data } : i
|
|
),
|
|
})
|
|
|
|
// UI State
|
|
case 'ADD_COMMAND_HISTORY':
|
|
return updateState({
|
|
commandBarHistory: [action.payload, ...state.commandBarHistory].slice(0, 50),
|
|
})
|
|
|
|
case 'SET_PREFERENCES':
|
|
return updateState({
|
|
preferences: { ...state.preferences, ...action.payload },
|
|
})
|
|
|
|
case 'RESET_STATE':
|
|
return { ...initialState, lastModified: new Date() }
|
|
|
|
default:
|
|
return state
|
|
}
|
|
}
|
|
|
|
// =============================================================================
|
|
// STORE
|
|
// =============================================================================
|
|
|
|
export interface SDKStoreOptions {
|
|
tenantId: string
|
|
userId: string
|
|
initialState?: Partial<SDKState>
|
|
onChange?: (state: SDKState) => void
|
|
}
|
|
|
|
export interface SDKStore {
|
|
getState: () => SDKState
|
|
dispatch: (action: SDKAction) => void
|
|
subscribe: (listener: (state: SDKState) => void) => () => void
|
|
}
|
|
|
|
export function createStore(options: SDKStoreOptions): SDKStore {
|
|
let state: SDKState = {
|
|
...initialState,
|
|
tenantId: options.tenantId,
|
|
userId: options.userId,
|
|
...options.initialState,
|
|
}
|
|
|
|
const listeners = new Set<(state: SDKState) => void>()
|
|
|
|
const getState = () => state
|
|
|
|
const dispatch = (action: SDKAction) => {
|
|
state = sdkReducer(state, action)
|
|
listeners.forEach(listener => listener(state))
|
|
options.onChange?.(state)
|
|
}
|
|
|
|
const subscribe = (listener: (state: SDKState) => void) => {
|
|
listeners.add(listener)
|
|
return () => listeners.delete(listener)
|
|
}
|
|
|
|
return { getState, dispatch, subscribe }
|
|
}
|