Documentation Index
Fetch the complete documentation index at: https://docs.prism.byescaleira.com/llms.txt
Use this file to discover all available pages before exploring further.
Advanced Features
PrismArchitecture ships with several advanced tools for debugging, persistence, and testing.
State Persistence
Persist state across app launches using PrismPersistenceStrategy. Three built-in strategies are provided:
Disk Persistence
Saves state as JSON files to the Documents directory:
let persistence = PrismDiskPersistence()
// Save
try persistence.save(appState, key: "app_state")
// Load
let loaded: AppState? = try persistence.load(key: "app_state")
// Clear
try persistence.clear(key: "app_state")
You can specify a custom directory:
let persistence = PrismDiskPersistence(
directory: FileManager.default.temporaryDirectory
)
UserDefaults Persistence
Stores state in UserDefaults for lightweight data:
let persistence = PrismUserDefaultsPersistence()
try persistence.save(settings, key: "user_settings")
let settings: Settings? = try persistence.load(key: "user_settings")
Keychain Persistence
Stores sensitive state in the system Keychain:
let persistence = PrismKeychainPersistence()
try persistence.save(credentials, key: "auth_credentials")
let creds: Credentials? = try persistence.load(key: "auth_credentials")
Custom Strategy
Implement PrismPersistenceStrategy for any backend:
struct CloudPersistence: PrismPersistenceStrategy {
func save<State: Codable & Sendable>(_ state: State, key: String) throws {
let data = try JSONEncoder().encode(state)
// Upload to your cloud service
}
func load<State: Codable & Sendable>(key: String) throws -> State? {
// Download from your cloud service
return nil
}
func clear(key: String) throws {
// Delete from your cloud service
}
}
Time-Travel Debugger
PrismTimeTravelDebugger records state snapshots at every action, letting you navigate through your app’s history:
let debugger = PrismTimeTravelDebugger<AppState>(maxSnapshots: 100)
// Record a snapshot after each action
debugger.record(state: store.state, action: "\(action)")
// Navigate through history
if debugger.canGoBack {
let snapshot = debugger.goBack()
store.replaceState(with: snapshot.state)
}
if debugger.canGoForward {
let snapshot = debugger.goForward()
store.replaceState(with: snapshot.state)
}
Inspecting Snapshots
Each snapshot captures the state, action description, timestamp, and position:
for snapshot in debugger.snapshots {
print("[\(snapshot.index)] \(snapshot.action) at \(snapshot.timestamp)")
}
// Jump to a specific point in history
let snapshot = debugger.goTo(index: 5)
Set maxSnapshots to limit memory usage. Old snapshots are evicted when the limit is exceeded.
Undo / Redo
PrismUndoRedoStack provides a classic undo/redo mechanism:
let undoRedo = PrismUndoRedoStack<AppState>(maxStackSize: 50)
// Before mutating state, push the current state
undoRedo.push(store.state)
store.send(.deleteItem(at: index))
// Undo
if undoRedo.canUndo, let previous = undoRedo.undo() {
store.replaceState(with: previous)
}
// Redo
if undoRedo.canRedo, let next = undoRedo.redo() {
store.replaceState(with: next)
}
The redo stack is automatically cleared when a new state is pushed, matching standard undo/redo behavior.
Derived Store
PrismDerivedStore provides a read-only view into a parent store that only notifies when the derived value actually changes:
struct AppState: PrismState {
var users: [User] = []
var selectedFilter: Filter = .all
}
// Derive a filtered user list
let filteredUsers = store.derive { state in
state.users.filter { $0.matches(state.selectedFilter) }
}
// Use in SwiftUI
struct UserListView: View {
let derived: PrismDerivedStore<AppState, [User]>
var body: some View {
List(derived.value) { user in
Text(user.name)
}
}
}
PrismDerivedStore uses Equatable on the local state to avoid unnecessary SwiftUI re-renders.
Testing with PrismTestStore
PrismTestStore wraps PrismStore with testing utilities for deterministic assertions:
@MainActor
func testIncrement() async {
let testStore = PrismTestStore(
initialState: CounterState(),
reduce: { state, action in
switch action {
case .increment: state.count += 1
case .decrement: state.count -= 1
}
return .none
}
)
testStore.send(.increment)
XCTAssertEqual(testStore.state.count, 1)
testStore.send(.decrement)
XCTAssertEqual(testStore.state.count, 0)
}
Testing Async Effects
Use waitForEffects() to wait for in-flight effects to complete before asserting:
@MainActor
func testFetchUsers() async {
let testStore = PrismTestStore(
initialState: UserState(),
reducer: UserReducer()
)
testStore.send(.fetchUsers)
XCTAssertTrue(testStore.state.isLoading)
// Wait for the async effect to complete
try await testStore.waitForEffects()
XCTAssertFalse(testStore.state.isLoading)
XCTAssertFalse(testStore.state.users.isEmpty)
}
Testing with Middleware
@MainActor
func testAnalyticsMiddleware() async {
var trackedEvents: [String] = []
let middleware = PrismSideEffect<AppState, AppAction> { _, action in
switch action {
case .purchase:
return .run { _ in trackedEvents.append("purchase") }
default:
return .none
}
}
let testStore = PrismTestStore(
initialState: AppState(),
reducer: AppReducer().handling(with: middleware)
)
testStore.send(.purchase(item))
try await testStore.waitForEffects()
XCTAssertEqual(trackedEvents, ["purchase"])
}
Summary
| Feature | Type | Purpose |
|---|
| Persistence | PrismPersistenceStrategy | Save/load state across launches |
| Time Travel | PrismTimeTravelDebugger | Navigate through state history |
| Undo/Redo | PrismUndoRedoStack | Classic undo/redo with stack |
| Derived Store | PrismDerivedStore | Efficient read-only projections |
| Test Store | PrismTestStore | Deterministic testing utilities |