Skip to main content

Arquero Patterns & Best Practices

Farseer Client uses Arquero extensively for data manipulation. This guide covers best practices and common patterns.

Best Practices

1. Use export.exportFormula() for Variable Data

// Good - Get variable DATA
const blob = await client.export.exportFormula({
format: ExportRequestFormatEnum.Csv,
formula: 'sum("Revenue", "Y2024")'
});
const data = aq.fromCSV(await blob.text());

// getVariable() is only for METADATA (ID, dimensions, formula)
const variable = await client.getVariable('Revenue');
console.log(variable.id); // Just the ID
console.log(variable.dimensionTagIds); // Linked dimension IDs

2. Calculate On-Demand Cells Before Exporting

// Ensure calculated fields are up-to-date
await client.cells.calculateOnDemandCells();
const data = await client.export.exportFormula({...});

// Or with timeout for long calculations
const calc = client.cells.calculateOnDemandCells();
const timeout = new Promise(r => setTimeout(r, 10 * 60 * 1000));
await Promise.race([calc, timeout]);

3. Use Batch Processing for Large Imports

const batchSize = 1000;
for (let i = 0; i < largeDataset.length; i += batchSize) {
const batch = largeDataset.slice(i, i + batchSize);
await importJob.addRows(batch);
}
await importJob.flushRows();

4. Use Labels for Import Job Management

const labels = [
'auto',
'sales-import',
`sales-import_${year}-${month}`,
'source:erp'
];

// Labels enable undoPrevious() to find matching imports
await importJob.undoPrevious();
await importJob.commit();

5. Validate Data Before Import

await client.initTagMap();
const products = await client.getDimensionMembersForTable('Product');
const validNames = products.map(p => p.name);

const rows = table.objects();
const validRows = rows.filter((r: any) => validNames.includes(r.product));
console.log(`${rows.length - validRows.length} invalid rows removed`);

Common Patterns

Dimension Member Lookup Map

async function getDimensionTableMap(
client: FarseerClient,
dimensionTableName: string
): Promise<Map<string, TagRepresentation>> {
const map = new Map<string, TagRepresentation>();
const members = await client.getDimensionMembersForTable(dimensionTableName);
members.forEach(m => map.set(m.name, m));
return map;
}

// Usage
const companiesMap = await getDimensionTableMap(client, 'Companies');
if (!companiesMap.has('Company A')) {
console.log('Company A does not exist');
}

Tag Navigation

await client.initTagMap();

// Get dimension name from table ID
const tableName = Array.from(client.tagMaps.dimensionTableMapByName.values())
.find(t => t.id === tableTagId)?.name ?? '';

// Get dimension names from variable's dimension tag IDs
const variable = await client.getVariable('Revenue');
const dimensionNames = variable.dimensionTagIds?.map(dimId =>
client.tagMaps?.mapById.get(dimId)?.name
).filter(Boolean);

console.log(`Revenue has dimensions: ${dimensionNames.join(', ')}`);

Using aq.escape() for Modern JavaScript

Arquero's expression parser doesn't support optional chaining (?.) or nullish coalescing (??). Use aq.escape():

import * as aq from 'arquero';

// Without escape (simple expressions only)
const filtered = table.filter(d => d.value > 0);

// With escape (modern JS syntax)
const filtered = table.filter(aq.escape((d: any) => d?.value !== null && d?.value !== 0));

// Derive with escape
const transformed = table.derive({
Years: aq.escape((d: any) => d.Years?.substring(1)),
Months: aq.escape((d: any) => d.Months?.substring(1))
});
tip

After .objects(), you get a plain JavaScript array. No aq.escape() needed:

const rows = table.objects().filter((d: any) => d?.value > 0);

Accessing Dimension Connections

Dimension connections in Arquero tables are arrays:

const { table } = await client.data.loadFarseerDimensionTable('Product');

// Always use [0] for connections
const filtered = table.filter(d => d?.['Category'][0] === 'Electronics');

const enriched = table.derive({
CategoryName: d => d?.['Category'][0] || 'Unknown',
SupplierName: d => d?.['Supplier'][0] || 'N/A'
});