Computed fields
Use the dependencies option instead of inline queries
A common pattern in legacy agents was to make database queries inside the get function. In the new agent, the dependencies option lets you declare which related fields you need, and the agent will fetch them automatically via JOIN, which is much faster.
Slow (inline query)
Fast (dependencies)
Best (import field)
agent.customizeCollection('post', postCollection => {
postCollection.addField('authorFullName', {
columnType: 'String',
dependencies: ['authorId'],
getValues: posts =>
posts.map(async post => {
// One query per post, very slow with many records
const author = await models.authors.findOne({ where: { id: post.authorId } });
return `${author.firstName} ${author.lastName}`;
}),
});
});
agent.customizeCollection('post', postCollection => {
postCollection.addField('authorFullName', {
columnType: 'String',
// Agent performs a single JOIN, much faster
dependencies: ['author:firstName', 'author:lastName'],
getValues: posts =>
posts.map(post => `${post.author.firstName} ${post.author.lastName}`),
});
});
// Define the field once on the author collection
agent.customizeCollection('author', authorCollection => {
authorCollection.addField('fullName', {
columnType: 'String',
dependencies: ['firstName', 'lastName'],
getValues: authors => authors.map(a => `${a.firstName} ${a.lastName}`),
});
});
// Import it on the post collection
agent.customizeCollection('post', postCollection => {
postCollection.importField('authorFullName', { path: 'author:fullName' });
});
Move async calls outside the hot loop
The new agent works in batch mode. If you have external service calls (APIs, etc.), fetch all records in a single request rather than one per record:
Slow (per-record)
Fast (batch)
getValues: users =>
users.map(async user => {
// One API call per user
const address = await geoWebService.getAddress(user.address_id);
return [address.line_1, address.city].join(', ');
}),
getValues: async users => {
// One API call for all users
const addresses = await geoWebService.getAddresses(users.map(u => u.address_id));
return users.map(user => {
const addr = addresses.find(a => a.id === user.address_id);
return [addr.line1, addr.city].join(', ');
});
},
Avoid duplicate queries across computed fields
If multiple computed fields depend on the same external data source, have one field fetch the data and let others depend on it:
agent.customizeCollection('users', users => {
users.addField('userInfo', {
columnType: { firstName: 'String', lastName: 'String' },
dependencies: ['id'],
getValues: async users => {
const ids = users.map(u => u.id);
return await authService.getUserInfo(ids); // single request
},
});
users.importField('firstName', { path: 'userInfo:firstName' });
users.importField('lastName', { path: 'userInfo:lastName' });
});
Segments
Use condition trees when possible
If your segment logic maps to a Forest condition tree, use it instead of running a raw query and filtering by IDs. The agent can push the condition tree down to the database as a native query, which is much faster.
// Fast: agent translates this to a native query
products.addSegment('InStock', () => ({
field: 'stock_count',
operator: 'GreaterThan',
value: 0,
}));
// Slower: fetches all IDs first, then filters
products.addSegment('InStock', async () => {
const ids = await db.query('SELECT id FROM products WHERE stock_count > 0');
return { field: 'id', operator: 'In', value: ids };
});
Filtering emulation
emulateFieldFiltering and emulateFieldSorting force the agent to retrieve all records to compute values. Use them only for collections with a low number of records (a few thousand at most). Prefer replaceFieldOperator for large collections.