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();
}
}
});
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';