Query DSL
The Arda Query DSL is a JSON-based language for filtering, sorting, and paginating records across the Arda Cloud API. It applies to both the Item and Kanban Card domains, and to any DataAuthorityEndpoint.
A query is a JSON object with three optional top-level keys:
{ "filter": { }, "sort": { }, "paginate": { }}All three are optional. Omitting filter returns all records. Omitting paginate uses server defaults.
Kotlin DSL Structure
Section titled “Kotlin DSL Structure”The Query DSL is defined by Kotlin data classes and sealed interfaces in Query.kt (cards.arda.common.lib.lang.query).
The top-level object:
data class Query( val filter: Filter = Filter.TRUE, val sort: Sort = Sort.NO_SORT, val paginate: Pagination = Pagination.FIRST_PAGE)Helper properties thisPage, nextPage, and previousPage are GZIPed and Base64 URL encoded string representations of the Query object, suitable for use as page cursors.
Filter
Section titled “Filter”A sealed interface representing a filtering condition:
Filter├── Filter.Literal│ ├── Filter.TRUE — always evaluates to true│ └── Filter.FALSE — always evaluates to false├── Filter.Composite│ ├── Filter.And(clauses: List<Filter>)│ └── Filter.Or(clauses: List<Filter>)├── Filter.Transform│ └── Filter.Not(clause: Filter)└── Filter.Term ├── Filter.UnaryTerm │ ├── Filter.IsNull(locator) │ └── Filter.IsNotNull(locator) ├── Filter.CompareTerm<FIELD> │ ├── Filter.Eq(locator, value) │ ├── Filter.Ne(locator, value) │ ├── Filter.Gt(locator, value) │ ├── Filter.Ge(locator, value) │ ├── Filter.Lt(locator, value) │ ├── Filter.Le(locator, value) │ └── Filter.RegEx(locator, value: String) └── Filter.SetTerm<FIELD> ├── Filter.In(locator, values: List<FIELD>) └── Filter.NotIn(locator, values: List<FIELD>)Locator is a type alias for String, identifying a field/property path (e.g., "fieldName", "nested.object.field").
data class Sort(val entries: List<SortEntry>) { companion object { val NO_SORT = Sort(emptyList()) }}
data class SortEntry(val key: Locator, val direction: SortDirection)enum class SortDirection { ASC, DESC }Pagination
Section titled “Pagination”data class Pagination(val index: Int, val size: Int) { companion object { val FIRST_PAGE = Pagination(0, 20) } val next: Pagination get() = copy(index = index + 1) val previous: Pagination? get() = if (index > 0) copy(index = index - 1) else null val start: Int get() = index * size}JSON Mapping
Section titled “JSON Mapping”Filter JSON Serialization
Section titled “Filter JSON Serialization”| Kotlin Filter | JSON Representation |
|---|---|
Filter.TRUE | true |
Filter.FALSE | false |
Filter.And | { "and": [<clause1>, <clause2>, ...] } |
Filter.Or | { "or": [<clause1>, <clause2>, ...] } |
Filter.Not | { "not": <clause> } |
Filter.IsNull | { "isNull": "fieldName" } |
Filter.IsNotNull | { "isNotNull": "fieldName" } |
Filter.Eq | { "locator": "fieldName", "eq": <value> } |
Filter.Ne | { "locator": "fieldName", "ne": <value> } |
Filter.Gt | { "locator": "fieldName", "gt": <value> } |
Filter.Ge | { "locator": "fieldName", "ge": <value> } |
Filter.Lt | { "locator": "fieldName", "lt": <value> } |
Filter.Le | { "locator": "fieldName", "le": <value> } |
Filter.RegEx | { "locator": "fieldName", "regex": "pattern" } |
Filter.In | { "in": { "locator": "fieldName", "values": [...] } } |
Filter.NotIn | { "notIn": { "locator": "fieldName", "values": [...] } } |
Instant values are serialized as ISO 8601 strings (e.g., "2023-10-27T10:30:00Z").
Deserialization uses smartParseJsonPrimitive to infer types: "true" → Boolean, "123" → Long, "123.45" → Double, ISO8601 string → Instant.
Sort JSON
Section titled “Sort JSON”{ "entries": [ { "key": "fieldNameA", "direction": "ASC" }, { "key": "fieldNameB", "direction": "DESC" } ]}Pagination JSON
Section titled “Pagination JSON”{ "index": 12, "size": 50 }JSON Examples
Section titled “JSON Examples”Primitive Term Examples
Section titled “Primitive Term Examples”// Boolean comparison{ "locator": "field", "eq": true }
// Number comparison{ "locator": "field", "gt": 42 }
// String comparison{ "locator": "field", "eq": "value" }
// Instant comparison{ "locator": "field", "eq": "2025-08-12T23:41:30.399755Z" }
// IN with numbers{ "in": { "locator": "field", "values": [1, 2, 3] } }
// IN with strings{ "in": { "locator": "field", "values": ["a", "b", "c"] } }
// NOT IN{ "notIn": { "locator": "field", "values": ["x", "y"] } }
// IS NULL{ "isNull": "field" }
// IS NOT NULL{ "isNotNull": "field" }
// REGEX{ "locator": "field", "regex": "^abc.*" }Composite Operator Examples
Section titled “Composite Operator Examples”// AND{ "and": [true, false] }
// OR{ "or": [true, false] }
// NOT{ "not": true }Full Query Example
Section titled “Full Query Example”Active items in a specific facility, sorted by location:
{ "filter": { "and": [ { "locator": "retired", "eq": false }, { "locator": "physical_locator_facility", "eq": "Building A" }, { "notIn": { "locator": "classification_type", "values": ["Deprecated", "Archive"] } }, { "not": { "isNull": "primary_supply_supplier" } } ] }, "sort": { "entries": [ { "key": "physical_locator_location", "direction": "ASC" } ] }, "paginate": { "index": 0, "size": 100 }}Compiling Queries to SQL (QueryCompiler)
Section titled “Compiling Queries to SQL (QueryCompiler)”The QueryCompiler translates Filter and Sort DSL components into Kotlin Exposed SQL operators.
QueryCompiler(private val tbl: EntityTable, private val translator: ExposedLocatorTranslator? = null)When a translator is provided, locator resolution uses the structured translator first (see below), then falls back to the table’s column lookup. Without a translator, locators resolve directly against the EntityTable’s column names.
Key methods:
filter(f: Filter): Op<Boolean>— converts a Filter to an Exposed SQL conditionwhere(f: Filter, asOf: TimeCoordinates): Op<Boolean>— combines filter with bitemporal visibilityordering(sort: Sort): List<Pair<Expression<*>, SortOrder>>— converts Sort to Exposed ordering (prepends bitemporal default sort)DecoratePaging(raw: SizedIterable<T>, p: Pagination): SizedIterable<T>— applies limit/offset
FilterExposedVisitor implements FilterVisitor<Op<Boolean>, EntityTable>, using the EntityTable to resolve locators to columns.
EntityServiceConfiguration and Structured Locator Translators
Section titled “EntityServiceConfiguration and Structured Locator Translators”EntityServiceConfiguration introspects a @Serializable entity payload class and builds an ExposedLocatorTranslator that maps JSON field names (camelCase property paths) to database columns. This allows API clients to use the same field names they see in JSON responses as filter/sort locators.
// Define once per entity typeval myQueryConfig = EntityServiceConfiguration.create(MyEntity.Entity::class) { opaque("settings") // exclude fields that are not filterable (e.g., JSON blobs)}.also { it.freeze() }
// Bind to a table to get a translatorval translator = myQueryConfig.bindToTable(MY_TABLE)The translator is then passed to QueryCompiler or set as the universe’s translator property:
class MyUniverse : AbstractScopedUniverse<...>() { override val translator by lazy { myQueryConfig.bindToTable(persistence.bt) }}Locator Resolution Order
Section titled “Locator Resolution Order”The ExposedLocatorTranslator.resolveColumn() method uses a multi-step resolution strategy:
- Structured path resolution — matches the locator as a JSON property path (e.g.,
identity.email→ theidentity_emailcolumn) - Exact column name match — falls back to
findColumn()which matches the raw SQL column name (e.g.,identity_email) - Case-insensitive and underscore-insensitive match — handles minor naming variations
This ensures backward compatibility: existing filters using snake_case column names continue to work alongside new camelCase JSON field paths.
Child Universe Translator Pattern
Section titled “Child Universe Translator Pattern”For child universes, the translator must be bound to a ChildTable. Due to invariant generics on ExposedLocatorTranslator, an explicit cast with a runtime guard is required:
override val translator by lazy { check(persistence.bt is ChildTable) { "MyChildUniverse requires a ChildTable, got ${persistence.bt::class}" } myChildQueryConfig.bindToTable(persistence.bt as ChildTable)}Locator Reference
Section titled “Locator Reference”Locators can be expressed as either JSON payload field paths (camelCase, matching the serialized field names) or database column names (snake_case). The structured EntityServiceConfiguration translator accepts both forms; a QueryCompiler without a configured translator accepts only raw column names.
| Style | Example | When to use |
|---|---|---|
| JSON field path | identity.email, cardQuantity.amount | Preferred for API clients — matches JSON response structure |
| Database column name | identity_email, card_quantity_amount | Legacy — still supported for backward compatibility |
Note: Universes configured with EntityServiceConfiguration accept both locator styles. Universes without a configured translator accept only raw column names.
Item Entity
Section titled “Item Entity”| Locator | Type | Description |
|---|---|---|
eid | uuid | Unique entity ID |
item_name | varchar(255) | Display name |
internal_sku | varchar(255) | Internal SKU / part number |
classification_type | varchar(255) | Item type (e.g., Raw Material) |
classification_sub_type | varchar(255) | Item subtype |
use_case | varchar(255) | Use case category |
gl_code | varchar(255) | GL code for accounting |
physical_locator_facility | varchar(255) | Facility name |
physical_locator_department | varchar(255) | Department |
physical_locator_location | varchar(255) | Storage location |
physical_locator_sub_location | varchar(255) | Sub-location |
image_url | varchar(8192) | Product image URL |
notes | varchar(8192) | Item notes |
card_notes_default | varchar(8192) | Default notes for auto-created cards |
retired | boolean | Soft-delete flag |
taxable | boolean | Tax-applicable flag |
primary_supply_supplier | varchar(255) | Primary supplier name |
primary_supply_sku | varchar(255) | Supplier part number |
primary_supply_order_method | varchar(255) | Order mechanism (see OrderMethod enum) |
primary_supply_url | varchar(8192) | Supplier URL |
primary_supply_order_quantity_amount | double | Order quantity |
primary_supply_order_quantity_unit | varchar(255) | Order unit (e.g., EA, BOX) |
primary_supply_unit_cost_value | double | Unit cost |
primary_supply_unit_cost_currency | varchar(5) | Currency code (see Currency enum) |
primary_supply_average_lead_time_length | integer | Lead time value |
primary_supply_average_lead_time_time_unit | varchar(10) | Lead time unit (see TimeUnit enum) |
secondary_supply_supplier | varchar(255) | Secondary supplier |
secondary_supply_sku | varchar(255) | Secondary supplier SKU |
secondary_supply_order_method | varchar(255) | Secondary order mechanism |
secondary_supply_url | varchar(8192) | Secondary supplier URL |
secondary_supply_order_quantity_amount | double | Secondary order quantity |
secondary_supply_order_quantity_unit | varchar(255) | Secondary order unit |
secondary_supply_unit_cost_value | double | Secondary unit cost |
secondary_supply_unit_cost_currency | varchar(5) | Secondary currency |
secondary_supply_average_lead_time_length | integer | Secondary lead time |
secondary_supply_average_lead_time_time_unit | varchar(10) | Secondary lead time unit |
default_supply | varchar(255) | Which supply chain is default |
default_supply_eid | uuid | Default supply entity ID |
card_size | varchar(255) | Kanban card size (see CardSize enum) |
label_size | varchar(255) | Label size |
breadcrumb_size | varchar(255) | Breadcrumb label size |
item_color | varchar(255) | Display color (see ItemColor enum) |
min_quantity_amount | double | Minimum quantity threshold |
min_quantity_unit | varchar(255) | Min quantity unit |
description | varchar(8192) | Extended description |
created_by | varchar(255) | Creator identifier |
created_at_effective | timestamp | When the record was effectively created |
created_at_recorded | timestamp | When the record was recorded in the system |
effective_as_of | timestamp | Bitemporal: when the fact was true |
recorded_as_of | timestamp | Bitemporal: when the fact was recorded |
Kanban Card Entity
Section titled “Kanban Card Entity”The fields below are drawn from the KanbanCard.Entity schema. The eid locator is confirmed to work against the database column directly. For other fields, the EntityServiceConfiguration translator maps JSON field paths to DB column names; if no translator is in effect, use the snake_case column name equivalent (e.g., serial_number instead of serialNumber).
| Locator (JSON path) | Type | Description |
|---|---|---|
eid | uuid | Unique card entity ID |
serialNumber | string | Card serial number |
item.eId | uuid | Referenced item entity ID |
item.name | string | Referenced item display name |
cardQuantity.amount | double | Order quantity amount |
cardQuantity.unit | string | Order quantity unit |
locator.facility | string | Physical location: facility |
locator.department | string | Physical location: department |
locator.location | string | Physical location: location |
locator.subLocation | string | Physical location: sub-location |
status | KanbanCardStatus | Current card status (see enum below) |
printStatus | KanbanCardPrintStatus | Current print status (see enum below) |
notes | string | Card-level notes |
retired | boolean | Soft-delete flag (from EntityRecord wrapper) |
Enum Reference
Section titled “Enum Reference”Enum values used in locator comparisons and returned in query results.
KanbanCardStatus
Section titled “KanbanCardStatus”| Value | Description |
|---|---|
AVAILABLE | Card is available / at rest |
REQUESTED | Reorder has been requested |
REQUESTING | Request is being processed |
IN_PROCESS | Order is being processed |
READY | Order is ready |
FULFILLING | Fulfillment in progress |
FULFILLED | Order fulfilled |
IN_USE | Item is in use |
DEPLETED | Item has been fully consumed |
UNKNOWN | Status not determined |
KanbanCardEventType
Section titled “KanbanCardEventType”| Value | Description |
|---|---|
REQUEST | Reorder requested |
ACCEPT | Request accepted |
SHELVE | Card shelved |
START_PROCESSING | Processing started |
COMPLETE_PROCESSING | Processing completed |
FULFILL | Fulfillment action |
RECEIVE | Item received |
USE | Item put into use |
DEPLETE | Item depleted |
WITHDRAW | Card withdrawn |
NONE | No event |
FAILED_ACTION | Action failed |
KanbanCardPrintStatus
Section titled “KanbanCardPrintStatus”NOT_PRINTED | PRINTED | LOST | DEPRECATED | RETIRED | UNKNOWN
OrderMethod
Section titled “OrderMethod”UNKNOWN | PURCHASE_ORDER | EMAIL | PHONE | IN_STORE | ONLINE | RFQ | PRODUCTION | TASK | THIRD_PARTY | OTHER
CardSize / LabelSize / BreadcrumbSize
Section titled “CardSize / LabelSize / BreadcrumbSize”SMALL | MEDIUM | LARGE | X_LARGE
ItemColor
Section titled “ItemColor”BLUE | GREEN | YELLOW | ORANGE | RED | PINK | PURPLE | GRAY
Currency
Section titled “Currency”USD | CAD | EUR | GBP | JPY | AUD | CNY | INR | RUB | BRL | ZAR | MXN | KRW | SGD | HKD | NZD | CHF
TimeUnit
Section titled “TimeUnit”MILLI | SECOND | MINUTE | HOUR | DAY | WEEK | STANDARD_MONTH | LONG_MONTH | STANDARD_FEBRUARY | LEAP_FEBRUARY | STANDARD_YEAR | CALENDAR_YEAR | LEAP_YEAR
SortDirection
Section titled “SortDirection”ASC | DESC
Applicable Endpoints
Section titled “Applicable Endpoints”The Query DSL request body is accepted by POST /query and POST /details endpoints on DataAuthorityEndpoint services. The following endpoints currently support it.
Item API
Section titled “Item API”| Endpoint | Method | Query DSL support |
|---|---|---|
/v1/item/item/query | POST | Full Query DSL (filter, sort, paginate) |
/v1/item/item/details | POST | Full Query DSL — returns hydrated item details |
/v1/item/item/{item-eid}/history | POST | Full Query DSL — scoped to a single item’s history |
Kanban Card API
Section titled “Kanban Card API”| Endpoint | Method | Query DSL support |
|---|---|---|
/v1/kanban/kanban-card/query | POST | Full Query DSL (filter, sort, paginate) |
/v1/kanban/kanban-card/details | POST | Full Query DSL — returns hydrated card details with embedded item |
/v1/kanban/kanban-card/{card-eid}/history | POST | Full Query DSL — scoped to a single card’s history |
All other endpoints on these APIs (single-record GET/PUT/DELETE, bulk-update, upload, lifecycle events) do not accept the Query DSL body. See the API Endpoint Catalog for the full list of endpoints and their OpenAPI specs.
Creating New Visitors
Section titled “Creating New Visitors”The FilterVisitor<R, TDATA> interface allows extending the DSL for new compilation targets.
interface FilterVisitor<R, TDATA: Any> { fun visit(filter: Filter, data: TDATA): Result<R>}Steps to create a new visitor:
- Determine target type
Rand contextual data typeTDATA - Implement
FilterVisitor<R, TDATA> - Handle all
Filtersubtypes in thewhenexpression; recursively callaccepton composite filters - Use
TDATAfor field mappings or configuration - Return
Result.success(value)orResult.failure(error)
The collectAll() extension function is useful for processing lists of Result<R>.
Copyright: © Arda Systems 2025-2026, All rights reserved