Polymorphic relationships
Filter relationship types and select type-specific fields with TYPEOF.
Some Salesforce references can point to more than one object type. Event.What is a common example.
Kysoql enables its polymorphic helpers only when the generated Salesforce metadata marks the reference as polymorphic.
Filter by the runtime object type
The virtual .Type qualifier can be selected and filtered:
import { db } from "./db";
const query = db
.selectFrom("Event")
.select(["Id", "What.Type"])
.where("What.Type", "in", ["Account", "Opportunity"])
.where("What.Type", "like", "Acc%");Equality and list values are constrained to the generated reference targets. .Type is not treated as sortable, groupable, or aggregatable.
If .Type is all you need, you do not have to generate every possible target object.
Select different fields with TYPEOF
Generate Event, Account, and Opportunity for this example:
import { db } from "./db";
const events = await db
.selectFrom("Event")
.select(["Id", "Subject"])
.selectTypeOf("What", (typeOf) =>
typeOf
.when("Account", ["Phone", "NumberOfEmployees"])
.when("Opportunity", ["Amount", "CloseDate"]),
)
.limit(25)
.execute();
type RelatedRecord = NonNullable<(typeof events)[number]["What"]>;
function isObjectType<T extends RelatedRecord["attributes"]["type"]>(
record: RelatedRecord | null,
objectType: T,
): record is Extract<RelatedRecord, { readonly attributes: { readonly type: T } }> {
return record?.attributes.type === objectType;
}
for (const event of events) {
const related = event.What;
if (isObjectType(related, "Account")) {
console.log(related.Phone, related.NumberOfEmployees);
} else if (isObjectType(related, "Opportunity")) {
console.log(related.Amount, related.CloseDate);
}
}Use the relationship name such as What, not the foreign-key field WhatId.
Each when() selection is checked against its target object. The result union is discriminated by Salesforce's returned attributes.type. Nullable lookups stay nullable.
Add an ELSE branch when the remaining targets share fields
A final .else(["Name"]) is available when the field is valid across every generated target that is not already covered by a when() branch.
Kysoql is conservative here. If a field is not safe across the remaining generated targets, add explicit branches or generate the missing target metadata instead of forcing the type with a cast.
TYPEOF cannot be mixed with aggregate queries or SELECT-function expressions. Do not also select a normal field path through the same polymorphic relationship.
Filtering on What.Type is still fine, which lets you narrow the runtime types without duplicating selections.
Nested polymorphic paths keep the nullability of their parent relationships. Normal relationship limits still apply.