SelectJoin & PropertyRelation in Mindbricks
Lightweight, relation-aware joins inside Get & List Business APIs. Mindbricks lets you connect DataObjects using Property Relations, then reuse those relations in many places (Business APIs, BFF DataViews, etc.).
1. Conceptual Overview
Mindbricks lets you connect DataObjects using Property Relations, then reuse those relations in many places (Business APIs, BFF DataViews, etc.).
SelectJoin is the “lightweight join” feature built on top of those relations:
A SelectJoin works like a left join in SQL, but it runs after the main data is fetched: Mindbricks first queries the main object, then runs extra queries to fetch related records based on the defined relations and collates those results into the response.
Key properties of SelectJoin:
-
It is configured inside the
selectClauseof Get and List Business APIs. -
It does not change the schema; it only enriches the API response.
-
It uses existing PropertyRelation metadata between objects.
-
It can join:
-
from main → joined object (one-to-one)
-
or from joined → main object (children join / one-to-many).
-
-
It can disambiguate multiple relations between the same objects using a
selectorstring.
2. PropertyRelation: How Objects are Linked
Before we talk about SelectJoin, we need the foundation: Property Relations.
A relation is defined on a property of a DataObject (the “foreign key” field), via hasRelation = true and PropertyRelationConfig.
From patterns.json:
"PropertyRelation": {
"__hasRelation.doc": "...",
"hasRelation": "Boolean",
"configuration": "PropertyRelationConfig",
"__activation": "hasRelation",
"__nullables": ["configuration"]
}
"PropertyRelationConfig": {
"relationName": "String",
"relationTargetObject": "DataObjectName",
"relationTargetKey": "PropRefer",
"onDeleteAction": "RelationOnDeleteAction",
"relationIsRequired": "Boolean"
}
2.1 What This Means
When you set hasRelation = true on a property, you’re saying:
“This property is a foreign key that points to a row in another DataObject.”
-
relationTargetObject– which object is being referenced (e.g.,auth:user,project,order). -
relationTargetKey– which field in the target object is referenced (usuallyid). -
relationName– human-friendly name for the relationship, used in generated code/docs. -
onDeleteAction– what to do when the target (parent) is deleted (doDelete,setNull). -
relationIsRequired– whether this FK must always point to a valid target.
2.2 One-to-One vs One-to-Many
With the new semantics:
-
Every relation is defined as “many-to-one” from the foreign key holder to the target:
-
e.g.,
order.userId→user.id -
In words: “Many orders belong to one user.”
-
-
From the source object point of view (where the property lives), it behaves like a one-to-one reference to the target:
- One order has one user.
-
From the target object point of view, the reverse is naturally one-to-many:
- One user has many orders.
Mindbricks uses this rule everywhere:
Any relation made to an object defines that object as the “parent” of those records that reference it.
So:
-
If you configure
order.userIdas a relation touser:-
orderis the child. -
useris the parent. -
From
ordertouser→ one-to-one reference. -
From
usertoorder→ zero-or-many orders.
-
3. Simple PropertyRelation Examples
3.1 Order → User
Goal: Link each order to the user who owns it.
In order DataObject, property userId:
{
"basicSettings": { "name": "userId", "type": "ID" },
"relationSettings": {
"hasRelation": true,
"configuration": {
"relationName": "orderUser",
"relationTargetObject": "auth:user",
"relationTargetKey": "id",
"onDeleteAction": "setNull",
"relationIsRequired": true
}
}
}
Meaning:
-
order.userIdreferencesauth:user.id. -
If user is deleted:
onDeleteAction = setNullwill null-outuserId. -
The relation is required on create (
relationIsRequired = true).
3.2 Project → Owner User
In project DataObject, property ownerId:
{
"basicSettings": { "name": "ownerId", "type": "ID" },
"relationSettings": {
"hasRelation": true,
"configuration": {
"relationName": "projectOwner",
"relationTargetObject": "auth:user",
"relationTargetKey": "id",
"onDeleteAction": "setNull",
"relationIsRequired": true
}
}
}
Now project has a one-to-one reference to user (its owner), and user implicitly has many projects.
3.3 Task → Project
In task DataObject, property projectId:
{
"basicSettings": { "name": "projectId", "type": "ID" },
"relationSettings": {
"hasRelation": true,
"configuration": {
"relationName": "taskProject",
"relationTargetObject": "project",
"relationTargetKey": "id",
"onDeleteAction": "doDelete",
"relationIsRequired": true
}
}
}
If project is deleted, all tasks are deleted (doDelete).
4. SelectJoin Pattern: Structure & Semantics
Now, with relations defined, we can use SelectJoin in selectClause of get and list Business APIs.
From the updated pattern:
"SelectJoin": {
"joinName": "String",
"condition": "MScript",
"joinedDataObject": "DataObjectName",
"selector": "String",
"properties": ["PropRefer"],
"sort": ["SortByItem"],
"topPick": "Integer"
}
With docs:
-
It is used in
SelectClauseSettings.selectJoins[]. -
It uses relations between main object and joinedDataObject, as configured via
PropertyRelation.
4.1 What SelectJoin Does
For a given get or list Business API:
-
Mindbricks executes the main query (fetch main object or list of objects).
-
For each SelectJoin:
-
It identifies the relevant relation(s) between main object and joined object, using
joinedDataObjectand optionalselector. -
It runs extra queries to fetch related records from
joinedDataObject:-
one record (one-to-one) or
-
multiple records (children list) depending on direction.
-
-
It picks specific properties (
properties) from those records. -
It attaches the joined data under the property name
joinNameof each main object in the API response.
-
-
The behavior is left join-like:
- If no related record exists, the joined property is
null(for one) or[](for many).
- If no related record exists, the joined property is
4.2 Fields in Detail
-
joinName-
A code-safe string used as:
-
Identifier in the SelectJoin config.
-
Property name in the returned JSON.
-
-
Example:
"owner","orders","lastUpdatedBy".
-
-
joinedDataObject(DataObjectName)-
Which DataObject to join with, e.g.,
"auth:user","order","project". -
This object must be related to the main object via one or more
PropertyRelations.
-
-
selector(String)-
New field that disambiguates which relation to use if more than one relation exists between main and joined objects.
-
Format: a special “map syntax” string:
foreignKey->localKey -
Not strictly a query, but a relation hint.
-
-
condition(MScript, optional)-
Boolean expression to decide whether to perform this join at code level:
- Example:
"this.includeOwner === true".
- Example:
-
Docs note:
“This condition can not reference the main object or main object list; for complex joins use a BFF View.” So use request parameters or session (
this.session,this.params), NOT row data.
-
-
properties(PropRefer[])-
Which fields of the joined object should be selected.
-
If empty, all fields are returned.
-
-
sort(SortByItem[], optional)-
Only relevant when join result is a list (children join).
-
Sorts the joined records before
topPickis applied. -
SortByItempattern:name,property,order ("asc"/"desc").
-
-
topPick(Integer, optional)-
How many joined records to keep after sorting in a children join.
-
If
topPick = 1→ result becomes a single object (instead of an array). -
Great for things like “latest order”, “oldest member” etc.
-
5. SelectJoin Relation Direction: One or Many
SelectJoin can traverse relations either way:
5.1 Main → Joined (one-to-one)
If the relation is defined on the main object pointing to joinedDataObject (like project.ownerId → user.id):
-
SelectJoin will return a single joined object (or
null). -
Example: “project owner user”.
5.2 Joined → Main (children / one-to-many)
If the relation is defined on the joined object pointing back to the main object (like order.userId → user.id):
-
SelectJoin will collect all joined records for that main object (children list).
-
Example: “user orders” inside a
getUserorlistUserAPI.
Mindbricks detects which direction to take based purely on relation definitions:
-
If it finds a PropertyRelation on main object that references joined → main→joined path.
-
If it finds a PropertyRelation on joined object that references main → joined→main path.
If both exist, or multiple different relations exist between these objects, selector tells which one to use.
6. The selector String Explained
The selector is a disambiguator when there are multiple relations between main and joined objects.
Docs:
“A relation selector is a special string with a map syntax
foreignKey->localKey. Leave null to select the first in case you are sure there is only one relation with the target data object. Eg. Main object isuser, joined object isproject,id->ownerId(user projects). Main object isprojectand joined object isuser,ownerId->id(project owner). Optional syntaxes:->or empty (first relation),->lastUpdateUserId,ownerId->, etc.”
To reconcile this, the safe interpretation (and easiest to communicate) is:
selector = mainSideProperty -> joinedSideProperty
You can think of it as:
“Match main[mainSideProperty] with joined[joinedSideProperty]”.
Let’s go through the examples:
6.1 Example: Main = user, Joined = project (user projects)
-
Relationship:
project.ownerIdreferencesuser.id. -
To get user’s projects in a
getUserAPI (main user, joined project), you want to join on:- main.id → joined.ownerId.
-
selector:
"id->ownerId"-
mainSide property:
id(on user) -
joinedSide property:
ownerId(on project)
-
6.2 Example: Main = project, Joined = user (project owner)
-
Relationship:
project.ownerIdreferencesuser.id. -
To get project’s owner in a
getProjectAPI:- main.ownerId → joined.id.
-
selector:
"ownerId->id"-
mainSide property:
ownerId(project) -
joinedSide property:
id(user)
-
6.3 Optional Shorthand Forms
Docs also mention partial shorthand:
-
""or"->"– means “use the first relation between main and joined objects.”- Only safe if there is exactly one relation.
-
"->lastUpdateUserId"– means “first relation whose joined side property islastUpdateUserId.” -
"ownerId->"– means “first relation whose main side property isownerId.”
These are syntactic sugar; for readability, it’s best to use a complete**mainField->joinedField** syntax.
7. SelectJoin Examples
7.1 Example 1 – Get Project with Owner User
Use case:
getProject API should return:
-
project fields (
id,name,ownerId) -
plus a nested
ownerobject (user)
Relations:
// In project DataObject
{
"name": "ownerId",
"relationSettings": {
"hasRelation": true,
"configuration": {
"relationName": "projectOwner",
"relationTargetObject": "auth:user",
"relationTargetKey": "id",
"onDeleteAction": "setNull",
"relationIsRequired": true
}
}
}
SelectClause:
"selectClause": {
"selectProperties": ["id", "name", "ownerId"],
"selectJoins": [
{
"joinName": "owner",
"joinedDataObject": "auth:user",
"selector": "ownerId->id",
"condition": "true",
"properties": ["id", "email", "fullname"],
"sort": [],
"topPick": null
}
]
}
Runtime behavior:
-
Main
projectloaded by ID. -
SelectJoin finds relation from
project.ownerId→auth:user.id. -
Queries
auth:userwhereid = project.ownerId. -
Attaches the selected user fields under
project.ownerin the response.
Example response:
{
"id": "proj-1",
"name": "My Project",
"ownerId": "user-123",
"owner": {
"id": "user-123",
"email": "owner@example.com",
"fullname": "Jane Doe"
}
}
7.2 Example 2 – Get User with Orders (Children Join)
Use case:
getUser API should return all orders of the user as orders.
Relations:
// In order DataObject
{
"name": "userId",
"relationSettings": {
"hasRelation": true,
"configuration": {
"relationName": "orderUser",
"relationTargetObject": "auth:user",
"relationTargetKey": "id",
"onDeleteAction": "setNull",
"relationIsRequired": true
}
}
}
Here, relation is defined on order pointing to user:
-
From order perspective: one-to-one reference.
-
From user perspective: children/one-to-many.
SelectClause (inside**getUser****):**
"selectClause": {
"selectProperties": ["id", "email", "fullname"],
"selectJoins": [
{
"joinName": "orders",
"joinedDataObject": "order",
"selector": "id->userId",
"condition": "true",
"properties": ["id", "totalAmount", "status", "createdAt"],
"sort": [
{
"name": "byCreatedAtDesc",
"property": "createdAt",
"order": "desc"
}
],
"topPick": null
}
]
}
How the selector works:
-
main object:
user -
joined object:
order -
id->userIdmeans: matchuser.idwithorder.userId.
Result:
-
For each user, Mindbricks fetches all orders where
userId=user.id. -
Sorted by
createdAt desc. -
Returned as an array in
user.orders.
Example response:
{
"id": "user-123",
"email": "alice@example.com",
"fullname": "Alice",
"orders": [
{ "id": "ord-10", "totalAmount": 120, "status": "paid", "createdAt": "..." },
{ "id": "ord-09", "totalAmount": 75, "status": "paid", "createdAt": "..." }
]
}
7.3 Example 3 – Get User with Last Order Only (topPick)
Same as above, but we only want the latest order as lastOrder.
"selectJoins": [
{
"joinName": "lastOrder",
"joinedDataObject": "order",
"selector": "id->userId",
"condition": "true",
"properties": ["id", "totalAmount", "status", "createdAt"],
"sort": [
{
"name": "byCreatedAtDesc",
"property": "createdAt",
"order": "desc"
}
],
"topPick": 1
}
]
- Now, join result is an array sorted by
createdAt desc, buttopPick = 1means we keep only the first record and return it as a single object.
Example response:
{
"id": "user-123",
"email": "alice@example.com",
"lastOrder": {
"id": "ord-10",
"totalAmount": 120,
"status": "paid",
"createdAt": "..."
}
}
7.4 Example 4 – Multiple Relations to Same Object (Using selector)
Suppose project has two properties referencing user:
-
ownerId– project owner -
lastUpdateUserId– last user who updated the project
Both have relationSettings pointing to auth:user.
Now we want:
-
getProjectto includeowneruser -
getProjectto includelastUpdateruser
In project DataObject:
{
"name": "ownerId",
"relationSettings": {
"hasRelation": true,
"configuration": {
"relationName": "projectOwner",
"relationTargetObject": "auth:user",
"relationTargetKey": "id",
"onDeleteAction": "setNull",
"relationIsRequired": true
}
}
},
{
"name": "lastUpdateUserId",
"relationSettings": {
"hasRelation": true,
"configuration": {
"relationName": "projectLastUpdater",
"relationTargetObject": "auth:user",
"relationTargetKey": "id",
"onDeleteAction": "setNull",
"relationIsRequired": false
}
}
}
SelectClause:
"selectClause": {
"selectProperties": ["id", "name", "ownerId", "lastUpdateUserId"],
"selectJoins": [
{
"joinName": "owner",
"joinedDataObject": "auth:user",
"selector": "ownerId->id",
"condition": "true",
"properties": ["id", "email", "fullname"],
"sort": [],
"topPick": null
},
{
"joinName": "lastUpdater",
"joinedDataObject": "auth:user",
"selector": "lastUpdateUserId->id",
"condition": "true",
"properties": ["id", "email", "fullname"],
"sort": [],
"topPick": null
}
]
}
Two SelectJoins to the same target object, distinguished by selector.
8. SelectJoin in List APIs
SelectJoin works the same way in List Business APIs; it just runs once per row in the result list.
8.1 Example – List Projects with Owner and Task Count
Assume:
-
Relations:
-
project.ownerId→user.id -
task.projectId→project.id
-
We want listProjects to return:
-
Basic project fields
-
ownerobject (user) -
tasksarray (list of tasks)
SelectClause:
"selectClause": {
"selectProperties": ["id", "name", "ownerId"],
"selectJoins": [
{
"joinName": "owner",
"joinedDataObject": "auth:user",
"selector": "ownerId->id",
"condition": "true",
"properties": ["id", "fullname"],
"sort": [],
"topPick": null
},
{
"joinName": "tasks",
"joinedDataObject": "task",
"selector": "id->projectId",
"condition": "true",
"properties": ["id", "title", "status"],
"sort": [
{
"name": "byTitle",
"property": "title",
"order": "asc"
}
],
"topPick": null
}
]
}
Response (simplified):
[
{
"id": "proj-1",
"name": "Main Project",
"ownerId": "user-1",
"owner": { "id": "user-1", "fullname": "Alice" },
"tasks": [ /* tasks for proj-1 */ ]
},
{
"id": "proj-2",
"name": "Side Project",
"ownerId": "user-2",
"owner": { "id": "user-2", "fullname": "Bob" },
"tasks": [ /* tasks for proj-2 */ ]
}
]
Behind the scenes, Mindbricks behaves like performing multiple “left joins” but implemented as:
-
Main query → fetch projects (with filters/sort).
-
Per SelectJoin:
-
Identify relations and queries.
-
Fetch and collate joined data efficiently.
-
9. When to Use SelectJoin vs Other Tools
SelectJoin is ideal when:
-
You have simple, well-defined relations between objects.
-
You want to enrich a Get or List response with related data.
-
You don’t want to build a full BFF DataView (global read model).
-
You don’t want to write extra actions like
FetchObjectActionfor each join.
Use BFF DataViews when:
-
You’re building complex, cross-service read models used by many screens.
-
You need stored views in Elasticsearch for performance or analytics.
Use**FetchObjectAction**** /FetchFromElasticAction when:**
- You want fully custom queries or logic not expressible as “follow relation X”.
10. ListJointFilterSettings & ListJointFilter
Join-like list filtering with pre-fetched ID lists
So far we covered PropertyRelation and SelectJoin, which enrich Get/List responses with related object data.
There is a sibling feature in List-type APIs:
ListJointFilterSettings + ListJointFilter let you filter the main list using conditions on a related DataObject, without writing an explicit join in your whereClause.
Conceptually:
-
SelectJoinadds data from a related object to each row. -
ListJointFilterfilters rows based on a related object.
Both rely on PropertyRelation metadata and both allow disambiguation via a selector string if there are multiple relations.
10.1 Pattern Overview
From patterns.json (slightly extended with selector):
"ListJointFilterSettings": {
"__operator.doc": "The combination operator of multi joint filters...",
"operator": "FilterOperator", // "AND" or "OR"
"filters": ["ListJointFilter"],
"__filters.single": "filter",
"__combination.default": "AND"
}
"FilterOperator": ["AND", "OR"]
"ListJointFilter": {
"__ListJointFilter.doc": "ListJointFilter definition object for a joint filter from the main object to another related data object to filter the main list result...",
"name": "String",
"joinedDataObject": "DataObjectName",
"condition": "MScript",
"whereClause": "MScript", // MScript Query on joined object
"selector": "String" // NEW: relation selector (assumed)
}
Note:
selectoris assumed as a new field, analogous toSelectJoin.selector, to resolve ambiguity when multiple relations exist between main and joined objects.
These appear inside a ListOptions:
"listOptions": {
"jointFilters": {
"operator": "AND",
"filters": [
{ /* ListJointFilter #1 */ },
{ /* ListJointFilter #2 */ }
]
}
}
10.2 What ListJointFilter Does
In a list Business API, the execution flow is conceptually:
-
Build the base whereClause from
WhereClauseSettings(if any). -
Apply permissionFilters, membershipFilters, and
searchFilter. -
For each
ListJointFilter:-
Use PropertyRelation metadata and
selector(if provided) to understand the relationship between main object andjoinedDataObject. -
Run a separate query on the
joinedDataObjectusingwhereClause(MScript Query) in the context of that joined object. -
From these results, collect the IDs of main objects related to those joined objects.
-
-
Combine the ID sets from all joint filters using
operator(“AND” / “OR”). -
Restrict the main query to only those main IDs that survived the combination.
-
Finally, fetch the main list and return results.
This behaves like a join filter, but implemented as:
- “Prefetch relevant joined objects → gather matching main IDs → restrict main query” instead of writing explicit SQL/ORM joins yourself.
10.3 Relation Direction
Just like SelectJoin, ListJointFilter can leverage relations in either direction:
-
Main → Joined (FK on main referencing joined)
-
Joined → Main (FK on joined referencing main)
Mindbricks determines direction based on PropertyRelation settings between the two DataObjects and uses that to map which IDs to collect.
10.4 The selector String for Joint Filters
Because there can be multiple relations between the same two objects, selector disambiguates which one to use.
We reuse the same mental model as for SelectJoin.selector:
selector = mainSideProperty -> joinedSidePropertymeaning: “matchmain[mainSideProperty]withjoined[joinedSideProperty]”
Examples:
-
Main:
user, Joined:order-
Relation:
order.userId→user.id. -
For “users who have orders”:
selector: "id->userId".
-
-
Main:
project, Joined:user(project owner)-
Relation:
project.ownerId→user.id. -
For “projects where owner matches some criteria”:
selector: "ownerId->id".
-
Shorthands (for completeness; same idea as SelectJoin):
-
""or"->"– first available relation between main & joined. -
"->fieldName"– first relation whose joined side field isfieldName. -
"fieldName->"– first relation whose main side field isfieldName.
But recommended: always use full mainField->joinedField for clarity.
10.5 Example: List Users Who Have Orders Over 200 USD
Goal: List only users that have at least one order with:
-
totalAmount > 200 -
currency = 'USD'
Data model:
-
order.userId→ relation toauth:user.id(user is parent). -
So: one user → many orders.
PropertyRelation (on**order.userId****):**
{
"basicSettings": { "name": "userId", "type": "ID" },
"relationSettings": {
"hasRelation": true,
"configuration": {
"relationName": "orderUser",
"relationTargetObject": "auth:user",
"relationTargetKey": "id",
"onDeleteAction": "setNull",
"relationIsRequired": true
}
}
}
List Business API: listUsers (main object = auth:user)
ListOptions with Joint Filter:
"listOptions": {
"jointFilters": {
"operator": "AND",
"filters": [
{
"name": "hasHighValueOrder",
"joinedDataObject": "order",
"selector": "id->userId", // user.id = order.userId
"condition": "true",
"whereClause": "({ totalAmount: { "$gt": 200 }, currency: { "$eq": 'USD' } })"
}
]
}
}
What Mindbricks does under the hood:
-
Executes
ordersearch with query:totalAmount > 200andcurrency = "USD".
-
Collects the
userIdof each matching order as potential main IDs. -
Restricts
listUserstoid IN collectedUserIds. -
Applies any other filters (whereClause, permissionFilters, etc.) on
user.
Result: Only users with at least one qualifying order show up in the list.
10.6 Combining Multiple Joint Filters with AND/OR
ListJointFilterSettings.operator defines how multiple joint filters are combined:
-
"AND"– main object must satisfy all joint filters. -
"OR"– main object must satisfy at least one joint filter.
10.6.1 Example: Users with High-Value Orders AND Open Tickets
Goal: List users who:
-
Have orders with
totalAmount > 200(USD), and -
Have at least one open support ticket.
Assume:
-
order.userId→user.id -
supportTicket.userId→user.id
ListOptions:
"listOptions": {
"jointFilters": {
"operator": "AND",
"filters": [
{
"name": "hasHighValueOrder",
"joinedDataObject": "order",
"selector": "id->userId",
"condition": "true",
"whereClause": "({ totalAmount: { "$gt": 200 }, currency: { "$eq": 'USD' } })"
},
{
"name": "hasOpenTicket",
"joinedDataObject": "supportTicket",
"selector": "id->userId",
"condition": "true",
"whereClause": "({ status: { "$eq": 'open' } })"
}
]
}
}
Process:
-
Prefetch
orderIDs →userIdset A. -
Prefetch
supportTicketIDs →userIdset B. -
AND→ intersectionA ∩ B. -
Only users in that intersection survive.
10.6.2 Example: Users with High-Value Orders OR Open Tickets
To get a “union” instead:
"jointFilters": {
"operator": "OR",
"filters": [
{ ...hasHighValueOrder... },
{ ...hasOpenTicket... }
]
}
Main IDs = A ∪ B.
10.7 Interaction with Other List Filters
ListJointFilter works alongside:
-
Base whereClause (
WhereClauseSettings) -
permissionFilters (ListPermissionFilter)
-
membershipFilters (ListMembershipFilter)
-
searchFilter (ListSearchFilter)
The combined filtering logic is roughly:
MainWhereClause
AND permissionFilters
AND membershipFilters
AND searchFilterIds
AND jointFilterIds
Where jointFilterIds is either:
-
IDsfrom one filter, or -
AND/ORcombination of multiple filter ID sets.
So you can express powerful queries like:
“List users who are in my tenant, have permission X, are members of project Y, whose name matches keyword K, and who have an order > $200.”
All this, purely declaratively.
10.8 When to Use Joint Filters vs BFF/DataViews
Use ListJointFilter when:
-
You want to constrain a List API based on related object conditions.
-
Your conditions are essentially “exists some joined record matching X.”
Use BFF DataViews when:
-
You’re building complex multi-object reporting or analytics views.
-
You want to pre-store read models in Elasticsearch.
Use FetchFromElasticAction or multiple BusinessApi actions when:
- You need very custom joining, cross-aggregation, or logic that doesn’t cleanly fit the “related object” model.
10.9 Summary: JointFilter at a Glance
-
What it is: A join-like filter mechanism for list APIs that uses prefetching and relation metadata.
-
What it does: Looks at a related object, collects main IDs that satisfy conditions, and filters the main list.
-
How it’s configured:
-
Inside
listOptions.jointFilters. -
operatorchooses AND/OR semantics across multiple filters. -
Each
ListJointFilterhasjoinedDataObject,selector,whereClause,condition.
-
-
Why it’s useful:
-
Express complex list constraints on related data declaratively, without writing joins or low-level query code.
-
Works in synergy with permission/membership/search filters and base whereClause.
-
Combined with PropertyRelation and SelectJoin, ListJointFilter completes a powerful trio:
-
PropertyRelation → models relationships.
-
SelectJoin → enriches responses with related data.
-
ListJointFilter → shapes which main objects are returned based on related data.
All of it driven by your patterns—no hand-written repository layer required.
11. Summary
Mindbricks’ relational and join-aware features form a clean, layered system:
1. PropertyRelation
Defines how DataObjects relate (one-to-one on the referencing side, one-to-many on the reverse). It is the foundation for all relation-driven logic.
2. SelectJoin
Enriches Get and List API responses by pulling in related records. Useful for adding nested data like:
-
project.owner
-
user.orders (children join)
-
project.lastUpdater
-
latest task, top N children, sorted lists
Supports relation disambiguation with a selector.
3. ListJointFilter
Filters List APIs by conditions on related objects. Equivalent to:
SELECT main FROM main WHERE EXISTS (related WHERE condition)
Also supports selector for choosing the correct relation path.
4. Both SelectJoin and ListJointFilter:
-
Use PropertyRelation metadata
-
Support multi-service and cross-object relationships
-
Require no custom SQL or ORM
-
Are fully declarative in patterns
-
Work for both main→joined and joined→main semantics
5. When to choose what:
| Goal | Feature |
|---|---|
| Include related data in response | SelectJoin |
| Filter main list by related data | ListJointFilter |
| Build complex read models | BFF DataViews |
| Implement advanced custom queries | FetchObjectAction / FetchFromElasticAction |
Together, these features give Mindbricks:
-
ORM-like relational decoding
-
SQL-like join capabilities
-
ES-backed query power
-
A strongly declarative experience
—all while generating clean, scalable service code.
Last updated Dec 29, 2025