Guidance for AI coding assistants working in this repository.
DataSource-Extensions is a multi-module Gradle (Kotlin DSL) library that wraps Caplin's com.caplin.platform.integration.java:datasource SDK with modern reactive APIs and a Spring Boot starter. Kotlin Coroutines Flow is the canonical internal representation; Java Flow.Publisher and Reactive Streams Publisher variants are thin adapters over it.
JDK 17. Spring Boot pinned to 3.5.x and Kotlin to 2.2.x (see gradle/libs.versions.toml). The common-library convention plugin applies io.spring.dependency-management and overrides Spring's BOM kotlin.version to our catalog value — without this, transitive Jackson updates raise kotlin-stdlib past what the compiler can read.
The repo is typically checked out on Windows. PowerShell uses .\gradlew.bat; Bash uses ./gradlew. Both work.
./gradlew classes # compile everything (what CI runs first)
./gradlew --continue check koverXmlReport # full test + coverage (what CI runs)
./gradlew test # tests only
./gradlew :reactive:datasourcex-kotlin:test # tests for one module
./gradlew :reactive:datasourcex-kotlin:test --tests "*.ClassName.methodName" # single test
./gradlew spotlessApply # auto-format Kotlin + Gradle files (REQUIRED before commit)
./gradlew spotlessCheck # verify formatting
./gradlew apiCheck # binary-compatibility check (see "Public API" below)
./gradlew apiDump # regenerate .api snapshots
./gradlew dokkaGenerate # build docs site (output in docs/build/dokka/html)
./gradlew :examples:spring-kotlin:bootRun # run an example app against a local Liberator
CAPLIN_USERNAME and CAPLIN_PASSWORD must be set in the environment (or as caplinUsername/caplinPassword Gradle properties) — without them, Gradle dependency resolution fails because the Caplin DataSource jar lives in a private Maven repo. See settings.gradle.kts.
reactive/api ← config DSL types (ActiveConfig, ChannelConfig, ContainerEvent, …)
reactive/core ← Binder + IFlowAdapter — does the real DataSource SDK plumbing
Everything funnels through here as kotlinx.coroutines Flow
reactive/kotlin ← Bind DSL for Flow } each contributes a Bind facade
reactive/java-flow ← Bind DSL for java.util.concurrent.Flow.Publisher
reactive/reactivestreams ← Bind DSL for org.reactivestreams.Publisher
spring ← spring-boot-starter-datasource (depends on reactive/kotlin)
util ← datasourcex-util — FlowMap, custom Flow operators, AntPatternNamespace,
Fory serialization helpers. No DataSource SDK dependency.
examples/ ← spring-java, spring-kotlin, spring-kotlin-chat — manual smoke tests
docs/ ← aggregated Dokka site (published to GitHub Pages from main)
The published Maven coordinates use the module's renamed name (datasourcex-kotlin, spring-boot-starter-datasource, etc.), not the Gradle path — set up in settings.gradle.kts.
The three reactive/{kotlin,java-flow,reactivestreams} modules are NOT three parallel hand-written implementations. Each runs a generateApi Gradle task (defined in buildSrc/src/main/kotlin/GenerateApi.kt, applied via common-reactive-library) that emits Kotlin source for the Bind, BindActive, BindActiveContainer, BindChannel, BindBroadcast DSL classes plus their Mapping/Json/Record flavours.
Each module's build.gradle.kts sets the variant: tasks.generateApi { publisherType = "kotlin" | "java" | "reactivestreams" }.
Implications:
- If you grep for
BindActiveJsonand find nothing, that's because it's generated. Run./gradlew :reactive:datasourcex-kotlin:generateApiand look inreactive/kotlin/build/generated/sources/generateApi/main/kotlin/. - The shape of the generated DSL lives in
buildSrc/src/main/kotlin/{Active,ActiveContainer,Channel,Broadcast,Functions}.kt. Edit those to change the generated API, not the build output. - All three published variants share the runtime path through
reactive/core/Binder.IFlowAdapteris the small bridge that converts each library's publisher type to/fromFlow<Any>.
spring depends on reactive/kotlin and exposes endpoints via custom annotations on top of Spring Messaging:
@DataServicemarks a controller.@DataMessageMapping("/subject/{var}")maps a subject pattern to a method (analogous to@MessageMapping).@IngressDestinationVariableextracts path variables.- Methods return
Flow<T>/Flux<T>/Publisher<T>for active subjects, or accept/returnFlowpairs for channels. - Wiring lives in
spring/src/main/kotlin/.../internal/—DataSourceAutoConfiguration,DataSourceMessageHandler,DataSourcePayloadMethodArgumentResolver,DataSourcePayloadReturnValueHandler. - The internal package is excluded from binary-compatibility validation (see
apiValidationblock inspring/build.gradle.kts).
The hands-on guide is spring/docs/GUIDE.md.
These libraries follow Semantic Versioning. The .api snapshots are the source of truth for what counts as the public surface, so any diff to an api/<module-name>.api file is a deliberate signal about the next release:
- Additions only (new classes, new members) → minor bump.
- Removals, renames, signature changes, or visibility reductions → major bump. Flag these in the PR description; don't slip them through.
- No
.apidiff → patch bump is fine.
org.jetbrains.kotlinx.binary-compatibility-validator is applied to every published library and enforces this: ./gradlew check runs apiCheck and fails if the public ABI drifts from the committed snapshot.
When you intentionally change public API: run ./gradlew apiDump, review the diff carefully (it tells you what version bump is required), and commit the updated .api files alongside the source change. Don't hand-edit them.
- Formatting:
ktfmtvia Spotless. Always run./gradlew spotlessApplybefore committing — CI doesn't auto-format. - Tests: JUnit 5 platform, Kotest assertions (
shouldBe), MockK for mocks, Turbine forFlowassertions. samplessource set: each library module has asrc/samples/kotlin/directory whose contents are pulled into Dokka output. Thesamplespackage is excluded from coverage (see rootbuild.gradle.kts).- Kover aggregates coverage across all published modules at the root project; per-module reports also exist.