Events Reference

dcupl uses an event system to notify your application of changes. Subscribe to events using the on() method on either the dcupl instance or individual lists.

Subscribing to Events

Global Events (dcupl instance)

// Subscribe to all events
const unsubscribe = dcupl.on((message) => {
  console.log('Event:', message.type, message);
});

// Later: unsubscribe
unsubscribe();

List Events

const list = dcupl.lists.create({ modelKey: 'Product' });

// Subscribe to list-specific events
const unsubscribe = list.on((message) => {
  if (message.type === 'list_updated') {
    console.log('List updated:', message);
  }
});

Event Message Structure

All events share a common message structure:

type DcuplUpdateMessage = {
  key: number;              // Unique event counter
  type: DcuplUpdateType;    // Event type (see below)
  scope: 'all' | 'global' | 'list';
  origin?: 'user' | 'loader';
  modelKey?: string;        // Related model
  listKey?: string;         // Related list
  method?: string;          // Method that triggered the event
  args?: any[];             // Arguments passed to the method
  result?: any;             // Return value (if connectMessageResult)
  duration?: number;        // Execution time in ms
  start?: number;           // Start timestamp
  end?: number;             // End timestamp
};

Event Types

Lifecycle Events

Event Scope Description
dcupl_initialized global dcupl has finished initialization
dcupl_destroyed global dcupl instance was destroyed
dcupl_updated_manually global dcupl.update() was called
dcupl_partial_update global Partial data update completed
dcupl_handle_datacontainer global Data container processed
dcupl.on((msg) => {
  if (msg.type === 'dcupl_initialized') {
    console.log('dcupl ready!');
    // Start using dcupl
  }
});

List Events

Event Scope Description
list_created global New list was created
list_updated list List data or query changed
list_destroyed list List was destroyed
// Track all list creations
dcupl.on((msg) => {
  if (msg.type === 'list_created') {
    console.log('New list:', msg.listKey, 'for model:', msg.modelKey);
  }
});

// Track specific list updates
const list = dcupl.lists.create({ modelKey: 'Product' });
list.on((msg) => {
  if (msg.type === 'list_updated') {
    // Re-render UI
    updateProductDisplay();
  }
});

Query Events

Event Scope Description
query_apply list Query condition added/modified
query_apply_options list Query options changed
query_remove list Query condition removed
query_reset list Query cleared
query_execute list Query executed
query_get list Query state retrieved
query_has list Query existence checked
query_one list Single item retrieved
query_many list Multiple items retrieved
result_updated list Query results changed
list.on((msg) => {
  switch (msg.type) {
    case 'query_apply':
      console.log('Filter applied:', msg.args);
      break;
    case 'result_updated':
      console.log('Results updated, refreshing...');
      refreshUI();
      break;
  }
});

Data Events

Event Scope Description
data_change global Data was modified (set/update/remove)
model_add global New model was registered
view_add global New view was registered
dcupl.on((msg) => {
  if (msg.type === 'data_change') {
    console.log('Data changed for model:', msg.modelKey);
    // Sync with server, invalidate cache, etc.
  }
});

Analytics Function Events

Event Scope Description
fn_facets list Facets calculated
fn_aggregate list Aggregation computed
fn_suggest list Suggestions generated
fn_groupBy list GroupBy executed
fn_pivot list Pivot table computed
fn_metadata list Metadata retrieved
filter_one list Single filter retrieved
filter_many list Multiple filters retrieved
list.on((msg) => {
  if (msg.type === 'fn_facets') {
    console.log(`Facets computed in ${msg.duration}ms`);
  }
});

Loader Events

Event Scope Description
loader_added global Loader registered with dcupl
loader_removed global Loader removed
loader_config_fetched global Loader config fetched from remote
loader_models_fetched global Model definitions loaded
loader_data_fetched global Data resources loaded
loader_scripts_fetched global Scripts loaded
loader_processed global Loader finished processing
dcupl.on((msg) => {
  switch (msg.type) {
    case 'loader_config_fetched':
      console.log('Config loaded');
      break;
    case 'loader_data_fetched':
      console.log('Data loaded');
      break;
    case 'loader_processed':
      console.log('Loader complete!');
      break;
  }
});

Common Patterns

Loading Indicator

let loadingCount = 0;

dcupl.on((msg) => {
  if (msg.type.startsWith('loader_') && msg.type !== 'loader_processed') {
    loadingCount++;
    showLoadingSpinner();
  }

  if (msg.type === 'loader_processed') {
    loadingCount--;
    if (loadingCount === 0) {
      hideLoadingSpinner();
    }
  }
});

Performance Monitoring

dcupl.on((msg) => {
  if (msg.duration && msg.duration > 100) {
    console.warn(`Slow operation: ${msg.type} took ${msg.duration}ms`, {
      model: msg.modelKey,
      list: msg.listKey,
      args: msg.args,
    });
  }
});

Debugging

// Log all events in development
if (process.env.NODE_ENV === 'development') {
  dcupl.on((msg) => {
    console.log(`[dcupl] ${msg.type}`, {
      scope: msg.scope,
      model: msg.modelKey,
      list: msg.listKey,
      duration: msg.duration ? `${msg.duration}ms` : undefined,
    });
  });
}

Sync with External Systems

dcupl.on(async (msg) => {
  if (msg.type === 'data_change' && msg.origin === 'user') {
    // User made a change, sync to server
    await syncToServer(msg.modelKey, msg.args);
  }
});

React Integration

function useDcuplEvent(dcupl: Dcupl | null, eventType: DcuplUpdateType) {
  const [lastEvent, setLastEvent] = useState<DcuplUpdateMessage | null>(null);

  useEffect(() => {
    if (!dcupl) return;

    const unsubscribe = dcupl.on((msg) => {
      if (msg.type === eventType) {
        setLastEvent(msg);
      }
    });

    return unsubscribe;
  }, [dcupl, eventType]);

  return lastEvent;
}

// Usage
function ProductList() {
  const event = useDcuplEvent(dcupl, 'data_change');

  useEffect(() => {
    if (event?.modelKey === 'Product') {
      refetchProducts();
    }
  }, [event]);
}

All Event Types

type DcuplUpdateType =
  // Lifecycle
  | 'dcupl_initialized'
  | 'dcupl_destroyed'
  | 'dcupl_updated_manually'
  | 'dcupl_partial_update'
  | 'dcupl_handle_datacontainer'
  // Data
  | 'data_change'
  | 'model_add'
  | 'view_add'
  // Lists
  | 'list_created'
  | 'list_updated'
  | 'list_destroyed'
  // Queries
  | 'query_apply'
  | 'query_apply_options'
  | 'query_remove'
  | 'query_reset'
  | 'query_execute'
  | 'query_get'
  | 'query_has'
  | 'query_one'
  | 'query_many'
  | 'result_updated'
  // Analytics
  | 'fn_facets'
  | 'fn_aggregate'
  | 'fn_suggest'
  | 'fn_groupBy'
  | 'fn_pivot'
  | 'fn_metadata'
  | 'filter_one'
  | 'filter_many'
  // Loaders
  | 'loader_added'
  | 'loader_removed'
  | 'loader_config_fetched'
  | 'loader_data_fetched'
  | 'loader_models_fetched'
  | 'loader_scripts_fetched'
  | 'loader_processed';