When the Solibo SDK is compiled to JavaScript via Kotlin Multiplatform, certain Kotlin types don’t map 1:1 to JS primitives. This document covers what you actually have to deal with — things that are solved transparently by the query layer or polyfills are noted but not belaboured.
| Kotlin type | In queries (reading) | In commands (writing) |
|---|---|---|
List<T> / KtList<T> |
Handled — query hooks call .asJsReadonlyArrayView() |
toKtList(arr) |
List<Long> / KtList<bigint> |
Handled | toIdList(arr) |
Map<K,V> / KtMap |
.asJsReadonlyMapView() available on the object |
KtMap.fromJsMap(map) |
Long (API parameter) |
n/a | toLong(n) |
Long (JSON serialization) |
Handled — BigInt polyfill converts to string | n/a |
Instant |
Kotlin object — use factory helpers | createInstant(date) |
LocalDate |
Kotlin object — use factory helpers | createLocalDate(year, month, day) |
enum class |
.name (string key) or .value (string value) |
Pass SDK enum constant directly |
| Sealed / polymorphic | Check .type string discriminator |
n/a |
solibo-query exports three helpers that eliminate the most common boilerplate:
import { toLong, toKtList, toIdList } from '@solibo/solibo-query'
toLong(companyId) // number | bigint | string → bigint
toKtList(myArray) // T[] → KtList<T>
toIdList(myIds) // (number | bigint | string)[] → KtList<bigint>
PagedList<T>All collection endpoints return a PagedList<T> instead of a plain list. The wrapper has three fields:
| Field | Type | Description |
|---|---|---|
.items |
KtList<T> |
The current page of results |
.meta |
Meta |
Metadata — .count is the number of items on this page |
.paging |
Paging |
Cursor tokens — .next / .previous for navigation; .next === "END_OF_LIST" means no more pages |
solibo-query)Paged list hooks use useInfiniteQuery. Data is accumulated across pages in data.pages:
const { data, fetchNextPage, hasNextPage } = useIssues({ companyId })
// Flatten all pages into one array
const allIssues = data?.pages.flatMap(page => page.items) ?? []
Each page in data.pages is already a plain JS object — page.items is a readonly T[] array (the bridge layer calls .asJsReadonlyArrayView() internally via fromKt(body)).
const body = (await sdk.api.task.indexTasks(toLong(companyId))).body()
const tasks = body.items.asJsReadonlyArrayView() // readonly T[]
const nextToken = body.paging.next // string | null
KtList / KtMapThe solibo-query hooks pass the full PagedList body through fromKt(), which recursively converts KtList to readonly T[]. React components receive plain arrays — no manual conversion needed.
If you work directly with an SDK response outside a hook, convert manually:
const items = response.body().items.asJsReadonlyArrayView()
// items is now readonly T[] — iterate, map, find, etc.
When submitting a list to an SDK command, use toKtList:
import { toLong, toKtList } from '@solibo/solibo-query'
import { SectionTagCommand } from '@solibo/solibo-sdk'
sdk.api.sections.multiCreateTagOnSection(
toLong(companyId),
toLong(sectionId),
toKtList(tags.map(t => new SectionTagCommand(t)))
)
For arrays of IDs specifically, use toIdList:
import { toIdList } from '@solibo/solibo-query'
sdk.api.thirdParty.sendWithDigiPost(
toLong(companyId),
toLong(thirdPartyId),
toIdList(publishTo) // (number | bigint)[] → KtList<bigint>
)
For maps:
import { KtMap } from '@solibo/solibo-sdk'
const ktMap = KtMap.fromJsMap(new Map(Object.entries(myObj)))
Use KtMutableList.fromJsArray() / KtMutableMap.fromJsMap() when the API requires a mutable variant.
Long — API parametersKotlin Long becomes a bigint on the JS side. All numeric ID parameters must be converted. Use toLong:
import { toLong } from '@solibo/solibo-query'
sdk.api.organization.showOrganization(toLong(organizationId))
sdk.api.task.indexTasks(toLong(companyId))
// Wrong — passes a plain number, which will cause a type error:
sdk.api.task.indexTasks(companyId)
toLong accepts number | bigint | string and returns a bigint primitive, which is what Kotlin/JS expects for Long parameters. (The BigInt(n).valueOf() pattern seen in older hooks is equivalent but more verbose.)
IDs returned in responses are also bigint. When comparing or using them as React Query keys, convert to string or number as needed:
const id = response.id.toString() // safe for display, keys, URL params
const idNum = Number(response.id) // only safe for values < 2^53
solibo-query ships a bigint-polyfill.ts that patches BigInt.prototype.toJSON to serialize as a string. This is imported at package entry and requires no action from consumers.
Instant and LocalDateKotlin date types are not JS Date objects. Use the SDK factory helpers when constructing commands:
import { createLocalDate, createInstant } from '@solibo/solibo-sdk'
// For LocalDate fields (e.g. startDate, endDate):
createLocalDate(2025, 1, 31) // year, month (1-based), day
// For Instant fields (e.g. validFrom, closedAt):
createInstant(new Date()) // wraps a JS Date
When reading date fields from responses, convert to a JS Date for display:
const date = new Date(response.createdAt.toEpochMilliseconds())
Do not pass a raw new Date() to a field typed as Instant — the SDK command constructor expects a Kotlin Instant.
Kotlin enums compile to singleton objects, not plain strings. Two properties are available:
.name — the enum entry name as declared in Kotlin ("APPROVED", "FOR_APPROVAL").value — the JSON serialization value (usually the same string)// Reading:
expense.status.name === 'APPROVED' // true
expense.status.value === 'APPROVED' // true (usually identical)
// Comparing with SDK constant (preferred — survives renames):
expense.status === ExpenseStatus.APPROVED
// Writing — pass the enum constant, not a string:
CreateExpenseCommand({ status: ExpenseStatus.FOR_APPROVAL, … })
Do not compare with a raw string using === on the enum object itself — it is an object, not a primitive.
The API returns polymorphic types (e.g. Resident is a sealed superclass; PersonResident and OrgResident extend it). The concrete type is identified by a type field whose value is the fully-qualified Kotlin class name:
if (resident.type === 'solibo.residents.query.domain.PersonResident') {
// narrow to PersonResident — access .email, .name, .mobile, etc.
}
instanceof checks do not work across the Kotlin/JS boundary.
null vs undefinedKotlin nullable fields (String?, Long?, etc.) arrive as null in JS, not undefined. Use null-coalescing or loose null checks:
resident.email ?? 'fallback' // correct
resident.email !== undefined // may miss null — incorrect
resident.email != null // correct (covers both)