ILSA allows you to create and run code snippets in TypeScript to enrich vehicle data. Think of TypeScript enrichers as Webhooks hosted by us. You can write code in the Backoffice, and we’ll execute it for all your vehicles.
You can create a new TypeScript Enricher in the Backoffice. You have to declare which fields you want to read from and which fields you want to write to. ILSA will automatically generate type definitions based on them to make your code type safe and help you prevent bugs. TypeScript will guide you to deal with any missing input values (almost all input values are optional). All output fields are optional: if you don’t return them, they’re left unchanged. You can explicitly return null for a field to unset it.
You must declare a function named handle like in the examples below.
You can throw an exception if your code unexpectedly fails. We’ll keep retrying until it succeeds. You can view these errors in the Backoffice.
The TypeScript enrichers aren’t all powerful yet, some things like editing prices won’t work yet. Contact us for assistance.
TIP
Don’t use global variables as we run concurrent enrichments.
TIP
Don’t use the current time without setting the expiry correctly.
Each time you save the code a new version will be created. Each of your instances using this TypeScript Enricher is linked to a specific version. You can manage which version is active with the Upgrade and Downgrade buttons. This allows you to activate code changes on your dev instance before applying it to your production instance.
You can use all ES2020 features. Newer features might or might not be supported. The editor in the Backoffice is Monaco and our execution runtime is Deno. There might be minor validation differences between the two, so before you Save or Test your enricher we’ll validate the code with Deno.
export function handle(input: Input): Output {
if(input.general?.make?.normalized == 'seadoo') {
return {
general: {
category: 'boat'
}
};
}
return {};
}
If you want to call an external API, you must select “Enable networking”. Your handle function should be an async function and return a Promise<Output>. See the example below.
To communicate with external servers, you can use the Fetch API.
export async function handle(input: Input): Promise<Output> {
const response = await fetch('https://example.com/?vin=' + encodeURIComponent(input.identification?.vin));
if(response.status == 404) {
// No extra information. Don't modify the vehicle.
return {};
}
if(!response.ok) {
throw new Error('No good: ' + response.status);
}
const data = await response.json();
return {
powertrain: {
emissions: {
class: data.emissions_class
}
}
};
}
If you have vehicle requirements that can’t easily be expressed with a Static Filter you can use a TypeScript enricher to implement your validation. Select “Enable reject functions” and you’ll get access to reject() and rejectWithCriteria().
Prefer rejectWithCriteria() because that can be translated into the advertisers language by DV. The argument to rejectWithCriteria() is a list of filters the vehicle doesn’t comply with.
reject() just allows a string that’ll be sent to the advertiser verbatim as the reason why their vehicle can’t be published on your platform.
Calling one of these functions ends the execution of your code.
import { rejectWithCriteriaMismatch } from './ilsa.ts';
import { Input, Output } from './generated.ts';
export function handle(input: Input): Output {
switch(input.general?.category) {
case 'car':
return {
yoursite: {
voertuigsoort: 'personenauto'
}
};
case 'van':
return {
yoursite: {
voertuigsoort: 'bedrijfswagen'
}
};
default:
rejectWithCriteriaMismatch([{
field: 'general.category',
operator: 'EQUALS',
value: ['car', 'van'],
}]);
}
}
If you’re using the current time in your enricher, you should tell ILSA when the result of the enrichment is no longer valid. This serves as both the cache expiry time, but also as the moment we’ll reenrich the vehicle to get a new result.
As an example, we have an internal enricher which calculates the age of the vehicle in months. We set the expiry at the time the vehicle turns one month older.
Setting very short expiry times (minutes) is not supported.
import { Meta } from './ilsa.ts';
import { Input, Output } from './generated.ts';
export function handle(meta: Meta, input: Input): Output {
const registered = new Date(input.history.first_registration.date);
const now = new Date();
let age = (now.getFullYear() - registered.getFullYear()) * 12 + (now.getMonth() - registered.getMonth());
if (now.getDate() < registered.getDate()) {
age--;
}
const nextMonth = new Date(registered);
nextMonth.setMonth(registered.getMonth() + age + 1);
meta.setExpiry(nextMonth);
return {
history: {
first_registration: {
age: age,
}
}
};
}
Handling arrays is a little more complex than regular fields. First we define two types of arrays: leaf arrays (like general.application.fields[]) and regular arrays (like images[]). Leaf arrays are straight-forward: The new value of the array is exactly what you return.
If you return array fields you overwrite any old value. Because there might be fields you haven’t selected (including hidden fields or fields we’ll create in the future), we include an _array_element_id in the input and output definition of the array. The _array_element_id represents all fields you haven’t selected but we still don’t want to lose.
If you rearrange or filter array elements, make sure you keep the _array_element_id attached to each entry. Often this will happen implicitly, like in the first example below.
To force you to think about whether an array element is a new element or an existing element, the _array_element_id field is required in each array element. If you’re actually adding new array entries, include _array_element_id: 'new'.
export function handle(input: Input): Output {
const ret = {} as Output;
ret.unsupported_properties = [];
for(const up of input.unsupported_properties ?? []) {
switch(up.label?.en_GB) {
case 'Cool car':
ret.cool = true;
break;
default:
ret.unsupported_properties.push(up);
}
}
return ret;
}
export function handle(input: Input): Output {
const images = input.images ?? [];
if(input.leasable) {
images.push({
url: 'https://www.example.com/lease_info.webp',
_array_element_id: 'new',
});
}
return {images};
}
Because we might add new enum options in the future, we also typecheck your code to see if it handles that. When you normally get an enum as 'car' | 'van' we’ll type check with 'car' | 'van' | string to verify you don’t have switch statements without a default etc.