Data API
The Data API (dcupl.data) provides methods for managing data in dcupl. It supports various operations including setting, updating, upserting, and removing data.
Overview
The Data API offers different strategies for data management:
- set: Replace all existing data for a model
- update: Update specific items (requires matching keys)
- upsert: Update existing or insert new items
- remove: Remove specific items
- reset: Clear all data for a model
All data operations are queued and processed when you call dcupl.init() or dcupl.update().
Methods
set()
Replaces all existing data for a model with the provided data.
Signature:
set(data: RawItem[], options: DataOptions): voidParameters:
| Parameter | Type | Required | Description |
|---|---|---|---|
data |
RawItem[] |
Yes | Array of data items |
options |
DataOptions |
Yes | Data operation options |
Returns: void
Examples:
dcupl.data.set(
[
{ key: 'p1', name: 'Product 1', price: 100 },
{ key: 'p2', name: 'Product 2', price: 200 },
],
{ model: 'Product' }
);dcupl.data.set(
[
{ id: 'p1', name: 'Product 1' },
{ id: 'p2', name: 'Product 2' },
],
{
model: 'Product',
keyProperty: 'id',
}
);dcupl.data.set([{ name: 'Product 1' }, { name: 'Product 2' }], {
model: 'Product',
autoGenerateKey: true,
});dcupl.data.set(newProducts, { model: 'Product' });
await dcupl.update();update()
Updates specific items by their keys. Items must already exist in the model.
Signature:
update(data: RawItem[], options: DataOptions): voidParameters:
| Parameter | Type | Required | Description |
|---|---|---|---|
data |
RawItem[] |
Yes | Array of items to update |
options |
DataOptions |
Yes | Data operation options |
Returns: void
Examples:
dcupl.data.update([{ key: 'p1', price: 150 }], { model: 'Product' });dcupl.data.update(
[
{ key: 'p1', price: 150, stock: 20 },
{ key: 'p2', price: 250, stock: 15 },
],
{ model: 'Product' }
);dcupl.data.update(
[
{ key: 'p1', price: 150 }, // Only price is updated
],
{ model: 'Product' }
);
await dcupl.update();dcupl.data.update([{ id: 'p1', price: 150 }], {
model: 'Product',
keyProperty: 'id',
});upsert()
Updates existing items or inserts new ones. If an item with the key exists, it's updated; otherwise, it's inserted.
Signature:
upsert(data: RawItem[], options: DataOptions): voidParameters:
| Parameter | Type | Required | Description |
|---|---|---|---|
data |
RawItem[] |
Yes | Array of items to upsert |
options |
DataOptions |
Yes | Data operation options |
Returns: void
Examples:
dcupl.data.upsert(
[
{ key: 'p1', name: 'Updated Product 1', price: 150 }, // Updates if exists
{ key: 'p4', name: 'New Product 4', price: 400 }, // Inserts if not exists
],
{ model: 'Product' }
);dcupl.data.upsert(
[
{ key: 'p1', price: 150 },
{ name: 'New Product' }, // Gets auto-generated key
],
{
model: 'Product',
autoGenerateKey: true,
}
);dcupl.data.upsert(fetchedData, { model: 'Product' });
await dcupl.update();remove()
Removes specific items from the model.
Signature:
remove(data: RawItem[], options: DataOptions): voidParameters:
| Parameter | Type | Required | Description |
|---|---|---|---|
data |
RawItem[] |
Yes | Array of items to remove (only key needed) |
options |
DataOptions |
Yes | Data operation options |
Returns: void
Examples:
dcupl.data.remove([{ key: 'p1' }], { model: 'Product' });dcupl.data.remove([{ key: 'p1' }, { key: 'p2' }, { key: 'p3' }], { model: 'Product' });dcupl.data.remove([{ id: 'p1' }], {
model: 'Product',
keyProperty: 'id',
});dcupl.data.remove(itemsToDelete, { model: 'Product' });
await dcupl.update();reset()
Removes all data for a specific model.
Signature:
reset(options: { model: string }): voidParameters:
| Parameter | Type | Required | Description |
|---|---|---|---|
options |
{ model: string } |
Yes | Options with model key |
Returns: void
Examples:
dcupl.data.reset({ model: 'Product' });dcupl.data.reset({ model: 'Product' });
dcupl.data.set(newData, { model: 'Product' });
await dcupl.update();dcupl.data.reset({ model: 'Product' });
dcupl.data.reset({ model: 'Category' });
await dcupl.update();apply()
Applies a data container directly (advanced usage).
Signature:
apply(container: DataContainer): voidParameters:
| Parameter | Type | Required | Description |
|---|---|---|---|
container |
DataContainer |
Yes | Data container to apply |
Returns: void
Examples:
const container: DataContainer = {
model: 'Product',
type: 'upsert',
data: products,
keyProperty: 'id',
};
dcupl.data.apply(container);dcupl.data.apply({
model: 'Product',
type: 'update',
data: updates,
keyProperty: 'productId',
autoGenerateKey: false,
autoGenerateProperties: true,
});Configuration Types
DataOptions
Configuration options for data operations.
Type Definition:
type DataOptions = {
model: string;
keyProperty?: string;
autoGenerateKey?: boolean;
};Properties:
| Property | Type | Default | Description |
|---|---|---|---|
model |
string |
- | Model key (required) |
keyProperty |
string |
'key' |
Property name to use as item key |
autoGenerateKey |
boolean |
false |
Auto-generate keys for items without one |
Examples:
{
model: 'Product';
}{
model: 'Product',
keyProperty: 'productId'
}{
model: 'Product',
autoGenerateKey: true
}{
model: 'Product',
keyProperty: 'id',
autoGenerateKey: true
}DataContainer
Advanced data container for direct application.
Type Definition:
type DataContainer = {
model: string;
type?: DataContainerType;
data: RawItem[];
keyProperty?: string;
autoGenerateKey?: boolean;
autoGenerateProperties?: boolean;
placeholderUid?: string;
};
type DataContainerType = 'upsert' | 'update' | 'set' | 'remove';Properties:
| Property | Type | Description |
|---|---|---|
model |
string |
Model key |
type |
DataContainerType |
Operation type |
data |
RawItem[] |
Data items |
keyProperty |
string |
Key property name |
autoGenerateKey |
boolean |
Auto-generate keys |
autoGenerateProperties |
boolean |
Auto-generate properties from data |
placeholderUid |
string |
Internal placeholder identifier |
Data Operation Patterns
Initial Data Load
Loading data for the first time.
// Define models
dcupl.models.set({
key: 'Product',
properties: [
{ key: 'name', type: 'string' },
{ key: 'price', type: 'float' },
{ key: 'status', type: 'string' },
],
});
dcupl.models.set({
key: 'Category',
properties: [{ key: 'name', type: 'string' }],
});
// Load data
dcupl.data.set(products, { model: 'Product' });
dcupl.data.set(categories, { model: 'Category' });
// Initialize
await dcupl.init();
// Now data is ready to queryIncremental Updates
Updating data after initial load.
// Initial load
dcupl.data.set(initialProducts, { model: 'Product' });
await dcupl.init();
// Later: Update specific items
dcupl.data.update([{ key: 'p1', price: 150, stock: 20 }], { model: 'Product' });
await dcupl.update();
// The update is efficient and only processes changed itemsBatch Operations
Performing multiple data operations in sequence.
// Queue multiple operations
dcupl.data.update(priceUpdates, { model: 'Product' });
dcupl.data.remove(discontinuedProducts, { model: 'Product' });
dcupl.data.upsert(newProducts, { model: 'Product' });
// Process all at once
await dcupl.update();
// All operations are applied in orderSync from API
Synchronizing data from an external API.
async function syncProducts() {
// Fetch from API
const response = await fetch('/api/products');
const products = await response.json();
// Upsert (update existing, insert new)
dcupl.data.upsert(products, { model: 'Product' });
await dcupl.update();
}
// Sync periodically
setInterval(syncProducts, 60000);Real-time Updates
Handling real-time data updates.
// WebSocket connection
const ws = new WebSocket('wss://api.example.com/products');
ws.onmessage = async (event) => {
const update = JSON.parse(event.data);
switch (update.type) {
case 'create':
dcupl.data.upsert([update.data], { model: 'Product' });
break;
case 'update':
dcupl.data.update([update.data], { model: 'Product' });
break;
case 'delete':
dcupl.data.remove([{ key: update.data.key }], { model: 'Product' });
break;
}
await dcupl.update();
};Partial Updates
Updating only specific fields efficiently.
// Update only price and stock fields
dcupl.data.update(
[
{
key: 'p1',
price: 150,
stock: 20,
// Other fields remain unchanged
},
],
{ model: 'Product' }
);
await dcupl.update();
// Batch partial updates
const priceUpdates = products.map((p) => ({
key: p.key,
price: p.price * 1.1, // 10% increase
}));
dcupl.data.update(priceUpdates, { model: 'Product' });
await dcupl.update();Data Migration
Migrating data between different schemas.
// Old data
const oldProducts = await fetchOldProducts();
// Transform to new schema
const newProducts = oldProducts.map((p) => ({
key: p.id,
name: p.productName,
price: p.cost,
category: p.categoryId,
status: p.active ? 'active' : 'inactive',
}));
// Load transformed data
dcupl.data.set(newProducts, { model: 'Product' });
await dcupl.init();Bulk Import
Importing large datasets efficiently.
// Import in batches
const batchSize = 1000;
for (let i = 0; i < largeDataset.length; i += batchSize) {
const batch = largeDataset.slice(i, i + batchSize);
if (i === 0) {
dcupl.data.set(batch, { model: 'Product' });
await dcupl.init();
} else {
dcupl.data.upsert(batch, { model: 'Product' });
await dcupl.update();
}
}Auto-Key Generation
Working with data that doesn't have keys.
// Data without keys
const rawData = [
{ name: 'Product 1', price: 100 },
{ name: 'Product 2', price: 200 },
];
// Set with auto-generated keys
dcupl.data.set(rawData, {
model: 'Product',
autoGenerateKey: true,
});
await dcupl.init();
// Items now have auto-generated keys
const products = dcupl.query.execute({ modelKey: 'Product' });
console.log(products[0].key); // Auto-generated keyCustom Key Properties
Using different property names as keys.
// Data with 'id' instead of 'key'
const products = [
{ id: 'prod-1', name: 'Product 1', price: 100 },
{ id: 'prod-2', name: 'Product 2', price: 200 },
];
// Set with custom key property
dcupl.data.set(products, {
model: 'Product',
keyProperty: 'id',
});
// Or configure in model
dcupl.models.set({
key: 'Product',
keyProperty: 'id',
properties: [
{ key: 'name', type: 'string' },
{ key: 'price', type: 'float' },
],
});
dcupl.data.set(products, { model: 'Product' });Data Validation
Ensuring data quality on import.
// Import with validation enabled
const dcupl = new Dcupl({
quality: {
enabled: true,
},
});
dcupl.models.set({
key: 'Product',
properties: [
{
key: 'name',
type: 'string',
quality: {
required: true,
validators: {
minLength: { value: 3 },
},
},
},
{
key: 'price',
type: 'float',
quality: {
required: true,
validators: {
min: { value: 0 },
},
},
},
],
quality: {
enabled: true,
},
});
// Data is validated on set/update
dcupl.data.set(products, { model: 'Product' });
await dcupl.init();
// Invalid items trigger quality errorsReplace All Data
Completely replacing a model's data.
// Clear and replace
dcupl.data.set(newProducts, { model: 'Product' });
await dcupl.update();
// Alternative: explicit reset
dcupl.data.reset({ model: 'Product' });
dcupl.data.set(newProducts, { model: 'Product' });
await dcupl.update();Conditional Updates
Updating data based on conditions.
// Fetch current state
const products = dcupl.query.execute({ modelKey: 'Product' });
// Prepare conditional updates
const updates = products
.filter((p) => p.stock < 10)
.map((p) => ({
key: p.key,
status: 'low_stock',
}));
// Apply updates
dcupl.data.update(updates, { model: 'Product' });
await dcupl.update();Complete Examples
E-Commerce Product Management
// Initial setup
const dcupl = new Dcupl();
dcupl.models.set({
key: 'Product',
properties: [
{ key: 'name', type: 'string' },
{ key: 'price', type: 'float' },
{ key: 'stock', type: 'int' },
{ key: 'status', type: 'string' },
],
references: [{ key: 'category', type: 'singleValued', model: 'Category' }],
});
// Load products
dcupl.data.set(products, { model: 'Product' });
await dcupl.init();
// Update prices
const priceUpdates = [
{ key: 'p1', price: 150 },
{ key: 'p2', price: 250 },
];
dcupl.data.update(priceUpdates, { model: 'Product' });
await dcupl.update();
// Add new products
const newProducts = [{ key: 'p4', name: 'Product 4', price: 400, stock: 50, status: 'active' }];
dcupl.data.upsert(newProducts, { model: 'Product' });
await dcupl.update();
// Remove discontinued
dcupl.data.remove([{ key: 'p1' }], { model: 'Product' });
await dcupl.update();Real-Time Dashboard
// Initialize with initial data
dcupl.data.set(initialMetrics, { model: 'Metric' });
await dcupl.init();
// Setup real-time updates
const eventSource = new EventSource('/api/metrics/stream');
eventSource.onmessage = async (event) => {
const metric = JSON.parse(event.data);
// Upsert new metrics
dcupl.data.upsert([metric], { model: 'Metric' });
await dcupl.update();
// UI automatically updates via list subscriptions
};Data Synchronization Service
class DataSyncService {
private dcupl: Dcupl;
private syncInterval: number;
constructor(dcupl: Dcupl) {
this.dcupl = dcupl;
}
async fullSync(model: string) {
const data = await this.fetchAll(model);
this.dcupl.data.set(data, { model });
await this.dcupl.update();
}
async incrementalSync(model: string, lastSync: Date) {
const updates = await this.fetchUpdates(model, lastSync);
if (updates.created.length > 0) {
this.dcupl.data.upsert(updates.created, { model });
}
if (updates.modified.length > 0) {
this.dcupl.data.update(updates.modified, { model });
}
if (updates.deleted.length > 0) {
this.dcupl.data.remove(
updates.deleted.map((id) => ({ key: id })),
{ model }
);
}
await this.dcupl.update();
}
startAutoSync(model: string, intervalMs: number) {
let lastSync = new Date();
this.syncInterval = setInterval(async () => {
await this.incrementalSync(model, lastSync);
lastSync = new Date();
}, intervalMs);
}
stopAutoSync() {
clearInterval(this.syncInterval);
}
private async fetchAll(model: string) {
const response = await fetch(`/api/${model}`);
return response.json();
}
private async fetchUpdates(model: string, since: Date) {
const response = await fetch(`/api/${model}/updates?since=${since.toISOString()}`);
return response.json();
}
}Offline-First Application
class OfflineDataManager {
private dcupl: Dcupl;
private pendingOperations: DataContainer[] = [];
constructor(dcupl: Dcupl) {
this.dcupl = dcupl;
this.loadFromLocalStorage();
}
async create(model: string, item: any) {
const operation = {
model,
type: 'upsert' as const,
data: [item],
};
// Apply locally
this.dcupl.data.apply(operation);
await this.dcupl.update();
// Queue for sync
this.pendingOperations.push(operation);
this.saveToLocalStorage();
// Try to sync if online
if (navigator.onLine) {
await this.sync();
}
}
async update(model: string, item: any) {
const operation = {
model,
type: 'update' as const,
data: [item],
};
this.dcupl.data.apply(operation);
await this.dcupl.update();
this.pendingOperations.push(operation);
this.saveToLocalStorage();
if (navigator.onLine) {
await this.sync();
}
}
async sync() {
if (this.pendingOperations.length === 0) return;
try {
// Send pending operations to server
await fetch('/api/sync', {
method: 'POST',
body: JSON.stringify(this.pendingOperations),
});
// Clear pending operations
this.pendingOperations = [];
this.saveToLocalStorage();
} catch (error) {
console.error('Sync failed:', error);
}
}
private saveToLocalStorage() {
localStorage.setItem('pendingOperations', JSON.stringify(this.pendingOperations));
}
private loadFromLocalStorage() {
const saved = localStorage.getItem('pendingOperations');
if (saved) {
this.pendingOperations = JSON.parse(saved);
}
}
}Performance Considerations
Partial Updates
dcupl automatically optimizes updates by detecting which items changed:
// Initial load
dcupl.data.set(largeDataset, { model: 'Product' });
await dcupl.init();
// Later: Update only 10 items out of 10,000
dcupl.data.update(smallUpdateBatch, { model: 'Product' });
await dcupl.update();
// Only processes the 10 changed itemsBatch Operations
Combine multiple operations for better performance:
dcupl.data.update([item1], { model: 'Product' });
await dcupl.update();
dcupl.data.update([item2], { model: 'Product' });
await dcupl.update();dcupl.data.update([item1, item2], { model: 'Product' });
await dcupl.update();Auto-Update Control
Control when lists update:
const dcupl = new Dcupl({
performance: {
autoUpdateLists: { enabled: false },
},
});dcupl.data.update(batch1, { model: 'Product' });
dcupl.data.update(batch2, { model: 'Product' });
await dcupl.update();myList.update();