@lighthouse/sdk

14.53.1 • Public • Published

Lighthouse SDK (JavaScript)

Introduction

The Lighthouse SDK is a data library built with Redux for communicating with the Lighthouse API. It abstracts API data communication, enabling reuse across multiple clients (Web, React Native), standardizes data structures for basic CRUD resources, and provides features such as offline support, optimistic updates, and state persistence.

Redux Version and External Documentation

The SDK uses Redux version 3.6.0, as specified in the package.json dependencies. This is a stable version of Redux that provides the core functionality for predictable state management.

For additional information about this version of Redux, refer to:

Key Redux concepts used in this SDK:

  • Store: The single source of truth for application state
  • Actions: Plain objects describing changes to state
  • Reducers: Functions that specify how state changes in response to actions
  • Middleware: Intercepts actions before they reach reducers

Store Structure

The Redux store in Lighthouse SDK is structured around a modular architecture. Each module represents a set of related functionality and data.

Core Components

  1. Store Configuration: The store is configured in src/store/configure.js, which sets up:

    • Redux middleware
    • State persistence
    • Offline support
    • Initial state
  2. Root Reducer: Combines all module reducers into a single reducer tree.

  3. State Persistence: Uses redux-persist to save and retrieve state from local storage.

  4. Middleware Stack:

    const middleware = composeEnhancers(
      applyMiddleware(
        thunk,                 // Handles async actions
        requestMiddleware,     // Manages API requests
        trackingMiddleware,    // Handles tracking events
        hookMiddleware,        // Allows registering actions hooks
        messagesMiddleware,    // Manages message registration/deregistration
        logsMiddleware,        // Processes logging queue
        actionLoggerMiddleware // Logs Redux actions
      )
    )

State Structure

The store's state is organized into several key sections:

  1. Module States: Each module has its own slice of state
  2. Offline State: Manages offline queue and network status
  3. Authentication State: Stores authentication information
  4. Application State: Stores application context
  5. Version State: Tracks SDK version for migration handling

Modules

In the Lighthouse SDK, a "module" is a self-contained unit of functionality that includes:

  1. Action Creators: Functions that create and return action objects
  2. Reducers: Functions that update state based on dispatched actions
  3. Selectors: Functions that extract and derive data from state
  4. Middleware (optional): For module-specific side effects

Modules follow a standard structure:

export const actions = {...}  // Action type constants
export const actionCreators = {...}  // Functions that return action objects
export const selectors = {...}  // Functions to extract data from state
export const reducer = {...}  // Functions to update state

Using a Module

Modules are imported and used through the SDK's getModule function:

import { getModule } from '@lighthouse/sdk'

const listId = 'all' // optional, defaults to 'default'
const location = getModule('location')

// In React components with Redux
function mapStateToProps(state) {
  const locationSelectors = location.selectors(state)
  return {
    locations: locationSelectors(listId).list(),
    currentLocation: locationSelectors.current(),
  }
}

function mapDispatchToProps(dispatch) {
  const { query, save, findById, remove } = location
  return {
    fetch: params => dispatch(query(listId, params)),
    save: (params, payload, id) => dispatch(save(params, payload, id)),
    findById: id => dispatch(findById(id)),
    remove: id => dispatch(remove(id)),
  }
}

Creating a New Module

To add a new CRUD module:

  1. Create a folder for the resource in /modules
  2. Add the new module reducer in /module/index.js
  3. Update the test for the root module

State Types in the Store

The SDK implements several state types to manage different aspects of data storage and manipulation:

1. Cache

The cache state is responsible for storing the actual data objects retrieved from or created for the API. Each entry in the cache contains:

  • entity: The actual data object
  • state: The current status of the data (resolved, resolving, save-failed, etc.)
  • error: Any error information if applicable
  • id: The unique identifier
  • optimistic: Flag indicating if the entity exists only locally (optimistic update)
  • originalEntity: Original data before optimistic updates (for rollback purposes)

States of cache items are defined in /src/constants/states.js:

export const RESOLVING = 'resolving'      // Requesting with server
export const RESOLVED = 'resolved'        // Request complete
export const SAVE_FAILED = 'save-failed'  // Saving failed
export const REMOVE_FAILED = 'remove-failed' // Removing failed
export const INVALIDATED = 'invalidated'  // Data is stale
export const ROLLED_BACK = 'rolled-back'  // Optimistic update failed

2. List

The list state manages collections of entities with:

  • ids: Array of entity IDs in the collection
  • params: The query parameters used to generate the list
  • state: The loading state of the list
  • error: Any error that occurred loading the list
  • links: Pagination links for the list
  • totalCount: Total number of items available
  • lastUpdated: Timestamp of the last update

Lists are identified by a listId (defaults to 'default') allowing multiple lists of the same entity type.

3. Current

The current state keeps track of the currently selected entity:

  • id: The ID of the currently selected entity

This is used for tracking the active entity across UI contexts.

Offline Mode with Redux-Offline

The Lighthouse SDK implements offline support using a custom version of redux-offline. The offline mode allows operations to continue working when the device is offline.

How Offline Mode Works

  1. Optimistic Updates: When using the optimistic: true parameter, changes are immediately applied to the local state and the request is added to the offline queue.

  2. Action Queue: The offline.outbox in the Redux state maintains a queue of pending network requests.

  3. Network Detection: The SDK detects network status using browser/device APIs and updates offline.online state accordingly.

  4. Request Processing:

    • When online, redux-offline processes its outbox queue sequentially
    • Each request attempts to sync with the server
    • Requests use a retry strategy with exponential backoff
  5. Conflict Resolution:

    • On success: The temporary ID is replaced with the server-provided ID
    • On failure: Actions are marked as "rolled back" and can be retried

Configuration

The offline functionality is configured in src/store/configure.js:

const offlineConfig = {
  batch,
  detectNetwork: detectNetwork || defaultDetectNetwork,
  discard: offlineDiscardFn,
  effect: offlineEffectFn(requestOptions),
  logger: offlineLogger || console,
  persist: noop,
  persistOptions: persistenceOpts,
  retry: offlineRetryFn,
}

Key retry settings:

const offlineDecaySchedule = [
  1000,     // 1 second
  1000 * 3, // 3 seconds
  1000 * 8, // 8 seconds
]

Usage Example

// Optimistic update with offline support
const params = {
  optimistic: true
}
const payload = {
  body: 'Hi Friend!'
}
// Message will be available in cache immediately
// Request will be queued for when network is available
message.save(params, payload)

Offline Flow

  1. Optimistic parameter in action creator creates a redux-offline action, assigning a temporary ID
  2. The request is added to the outbox queue
  3. When online, the queue is processed:
    • On success: Entity is updated with database ID
    • On failure: Entity is marked as "rolled-back" for retry

Important Considerations

  • Only save/update requests work offline
  • GET requests require a network connection
  • Previously fetched data can be used for offline operations but may be stale
  • Network status is detected via browser/device APIs
  • Non-optimistic requests use the request middleware

Redux Middleware

The SDK uses several middleware layers to handle different aspects of application logic:

1. Request Middleware

Located in src/middleware/request.js, this middleware:

  • Handles all API requests that aren't optimistic updates
  • Manages request lifecycle with request/success/failure actions
  • Supports retry logic for failed requests
  • Formats query parameters and request bodies
  • Handles authentication errors

2. Messages Middleware

Located in src/middleware/messages/index.js, this middleware:

  • Manages the registration/deregistration of users for messaging
  • Listens for application switching and authentication events
  • Handles message delivery between applications

3. Logs Middleware

Located in src/modules/logs/middleware/index.js, this middleware:

  • Processes logs in the logging queue
  • Throttles log sending to avoid flooding the server
  • Ensures logs are sent when online
  • Handles log failures and retries

4. Tracking Middleware

Located in src/modules/tracking/middleware/index.js, this middleware:

  • Intercepts tracking actions
  • Forwards tracking events to external tracking services

5. Redux-Thunk

A standard middleware that allows action creators to return functions instead of action objects, enabling async logic.

6. Redux-Hook Middleware

Allows components to hook into actions and add side effects.

Local Storage Persistence

The SDK uses redux-persist to save and rehydrate state to/from local storage:

Configuration

Persistence is configured in src/store/configure.js:

const persistenceOpts = Object.assign(
  {},
  defaultPersistenceOpts,
  {
    storage: storageAdapter,
  },
)

The storageAdapter is provided by the application and must implement the localStorage interface (getItem, setItem, removeItem).

How Persistence Works

  1. State Serialization: The Redux state is serialized and stored in local storage
  2. Debounced Writes: Changes are debounced (50ms default) to prevent excessive writes
  3. Hydration on Startup: When the application starts, state is retrieved from storage
  4. Version Checking: Major version changes trigger state reset to prevent errors
  5. Sanitization: Stored state is sanitized before use to fix offline state issues

State Migration

The SDK handles state migration when the SDK version changes:

const hasMajorVersionChange = majorVersionChange(state)
if (hasMajorVersionChange) {
  reduxLogger.warn('Major version change detected, resetting state...')
  state = initialState
}

Potential Pitfalls

  1. Optimistic Updates Conflicts:

    • Local updates might conflict with server state
    • Potential Solution: Implement proper conflict resolution in UI
  2. Offline Queue Management:

    • The outbox queue can grow large during extended offline periods
    • Failed requests could block subsequent requests
    • Potential Solution: Monitor queue size and implement manual queue management
  3. Temporary IDs:

    • Optimistic updates use temporary IDs that change after server sync
    • Solution: Use the current selector which will handle the transition of temporary/permanent IDs
  4. Rollback Handling:

    • Failed optimistic updates become "rolled-back" but still exist in cache
    • Solution: There are manual retry actions in the mobile app

Troubleshooting Tips

1. Debug Mode

Enable Redux Logger for detailed action logs:

const config = {
  reduxLoggerPredicate: () => true, // Enable logging all actions
}

2. Network Issues

  • Check offline.online state to verify network detection
  • Inspect offline.outbox for pending requests
  • Review retry configuration if requests fail repeatedly

3. Data Inconsistencies

  • Inspect cache state for entities with state: 'rolled-back'
  • Look for optimistic updates that might have failed
  • Check for entities with temporary IDs (typically CUID format)

4. State Persistence Problems

  • Verify the storage adapter implementation
  • Check for localStorage quota limits
  • Consider clearing persisted state during major version upgrades

5. Common Error Patterns

  • Authentication errors: Re-authenticate the user
  • Network failures: Implement appropriate retry UI
  • Validation errors: Show detailed field-level errors from server
  • Stale data: Implement refresh patterns for invalidated data

Readme

Keywords

none

Package Sidebar

Install

npm i @lighthouse/sdk

Weekly Downloads

159

Version

14.53.1

License

ISC

Unpacked Size

9.23 MB

Total Files

347

Last publish

Collaborators

  • wickie
  • willmcclellan
  • chrishurt
  • taherashorna
  • raymundogs
  • nirodhaperera-ifs
  • andyf
  • mitchell.hoppe
  • thisuradodangoda-ifs
  • aharvey101
  • pubudithajayasekara
  • ray_gs3