用 Codex 或 Claude 帮你安装 复制这段 Prompt,粘贴到 Codex、Claude 或其他助手里,让它检查 Skill 页面并帮你完成安装。
直接命令不会经过审查 Prompt;运行前请先检查来源。
npx skills add https://github.com/navikt/helved-utbetaling --skill libs-reference命令会保持在同一行。复制前请横向滚动并检查完整内容。
想先保存到本地?可下载 SkillsMP 当前能够提供的文件。
正在显示 SKILL.md
基于 SOC 职业分类
| name | libs-reference |
| description | Library API reference for helved-utbetaling internal /libs |
| license | MIT |
| compatibility | opencode |
| metadata | {"audience":"ai-assistant","language":"kotlin","framework":"ktor","domain":"nav-payment-system"} |
Parent context: See root
AGENTS.mdfor conventions, build system, and patterns.
Load this skill when:
libs/jdbc, libs/kafka, libs/mq, libs/ws, libs/ktor, libs/auth, libs/http, libs/cache, libs/tracing, or libs/utils.Application.<appName>() function.libs/mq vs libs/ws for outbound integration).libs/jdbc-test, libs/kafka-test, libs/mq-test, libs/auth-test, or libs/ktor-test.Location: libs/jdbc/main/libs/jdbc/
Main Exports:
Dao<T> interface: Pattern for database operationsCoroutineDatasource: CoroutineContext element for connection managementDataSource.context(): Wrap a DataSource as a CoroutineDatasourceMigrator: Schema migration systemtransaction { }: Suspending DB transaction blockconcurrency.*: Connection management primitivesUsage Pattern:
class UserService(private val jdbcCtx: CoroutineDatasource) {
suspend fun findById(id: Int): User? = withContext(jdbcCtx) {
UserDao.query("SELECT * FROM ${UserDao.table} WHERE id = ?") { stmt ->
stmt.setInt(1, id)
}.firstOrNull()
}
suspend fun insert(user: User): Int = withContext(jdbcCtx) {
UserDao.update("INSERT INTO ${UserDao.table} (name) VALUES (?)") { stmt ->
stmt.setString(1, user.name)
}
}
}
object UserDao : Dao<User> {
override val table = "users"
override fun from(rs: ResultSet) = User(
id = rs.getInt("id"),
name = rs.getString("name")
)
}
Key Patterns:
jdbcCtx once at the app entry point: val jdbcCtx = Jdbc.initialize(config.jdbc).context()jdbcCtx: CoroutineDatasource explicitly through DI to services/routes/topology functionsDao<T> (no DI in DAOs themselves)withContext(jdbcCtx) for DB operations -- never reach for a globaltransaction { } for multi-statement atomic operationsquery() returns List<T>, update() returns affected row countquery(sql, mapper = { rs -> CustomType(...) }) { stmt -> ... }Dependencies: HikariCP, PostgreSQL driver, kotlinx-coroutines
Location: libs/jdbc-test/main/libs/jdbc-test/
Main Exports:
PostgresContainer: Testcontainers PostgreSQL setupJdbcUtils: Test utilitiesUsage Pattern:
object TestRuntime {
val datasource: DataSource by lazy { Jdbc.initialize(postgres.config) }
val context: CoroutineDatasource by lazy { datasource.context() }
}
@Test fun `test database operation`() = runTest(TestRuntime.context) {
UserDao.insert(User(name = "Test"))
val user = UserDao.findById(1)
assertNotNull(user)
}
Reusable Containers: Containers persist between test runs. If stopped: docker start postgres
Location: libs/kafka/main/libs/kafka/
Main Exports:
topology { }: DSL for building Kafka Streams topologiesTopic<K, V>: Type-safe topic abstractionTable<K, V>, Store<K, V>: State store abstractionsSerde: Serialization helpers (JSON, XML, etc.)consume(), .map(), .branch(), .produce()ConsumerProducer: Vanilla Kafka producer/consumer wrappersKafkaStreams: Streams runtimeUsage Pattern:
object Topics {
val input = Topic<String, PaymentRequest>(
name = "helved.input.v1",
keySerde = Serdes.String(),
valueSerde = JsonSerde(PaymentRequest::class)
)
val output = Topic<String, PaymentResult>(
name = "helved.output.v1",
keySerde = Serdes.String(),
valueSerde = JsonSerde(PaymentResult::class)
)
}
val topology = topology("app-name") {
Topics.input.consume { key, value ->
val result = processPayment(value)
Topics.output.produce(key, result)
}
}
// With state store
val topology = topology("app-name") {
val store = Topics.stateStore.globalKTable()
Topics.input.consume { key, value ->
val state = store[key]
// Process with state
Topics.output.produce(key, result)
}
}
Key Patterns:
Topic objects in a Topics object per appconsume { } for stream processing.map(), .filter(), .branch() for transformationsglobalKTable() for read-only state storesResult.catch { } pattern for error handling in processorsDependencies: Kafka Streams, Jackson for JSON
Location: libs/kafka-test/main/libs/kafka-test/
Main Exports:
StreamsMock: In-memory Kafka Streams testingTestTopic<K, V>: Test topic wrapperProducerConsumerFake: Fake producer/consumerVanillaKafkaMock: Vanilla Kafka testing utilitiesUsage Pattern:
object TestRuntime {
val streamsMock = StreamsMock()
}
@Test fun `test kafka topology`() {
val topology = createTopology()
TestRuntime.streamsMock.start(topology)
val inputTopic = TestTopic(Topics.input)
val outputTopic = TestTopic(Topics.output)
inputTopic.produce("key1", PaymentRequest(...))
val output = outputTopic.consume()
assertEquals("key1", output.key)
assertNotNull(output.value)
}
Location: libs/mq/main/libs/mq/
Main Exports:
DefaultMQ: MQ connection managerMQProducer: Send messages to MQ queuesMQConsumer: Consume messages from MQ queuesUsage Pattern:
val mq = DefaultMQ(config)
val producer = mq.createProducer("QUEUE.NAME")
producer.send(oppdragXml.toByteArray())
val consumer = mq.createConsumer("KVITTERING.QUEUE")
consumer.receive { message ->
// Process kvittering
}
Dependencies: JMS, IBM MQ client
Location: libs/mq-test/main/libs/mq-test/
Main Exports:
MQFake: In-memory MQ fake for testingUsage Pattern:
object TestRuntime {
val mqFake = MQFake()
}
@Test fun `test MQ integration`() {
val producer = TestRuntime.mqFake.createProducer("QUEUE")
producer.send("test message".toByteArray())
val messages = TestRuntime.mqFake.getMessages("QUEUE")
assertEquals(1, messages.size)
}
Location: libs/http/main/libs/http/
Main Exports:
HttpClientFactory.new(): Creates Ktor HTTP client with sensible defaultsUsage Pattern:
val client = HttpClientFactory.new {
install(JsonFeature) {
serializer = JacksonSerializer()
}
}
val response = client.get("https://api.example.com/data")
Dependencies: Ktor client (CIO engine)
Location: libs/ktor/main/libs/ktor/
Main Exports:
CallLog: Request timing and logging pluginUsage Pattern:
fun Application.module() {
install(CallLog) // Logs request timing and path
install(ContentNegotiation) { jackson() }
routing {
get("/") { call.respond("OK") }
}
}
Location: libs/ktor-test/main/libs/ktor-test/
Main Exports:
KtorRuntime: Test server and client setupUsage Pattern:
object TestRuntime {
val ktorRuntime = KtorRuntime(Application::myApp)
val httpClient = ktorRuntime.client
}
@Test fun `test HTTP endpoint`() = runTest {
val response = TestRuntime.httpClient.get("/api/health")
assertEquals(HttpStatusCode.OK, response.status)
}
Location: libs/auth/main/libs/auth/
Main Exports:
TokenValidator: JWT token validation (Azure AD, TokenX)TokenClient: Token acquisition clientAzureTokenProvider: Azure AD token providerTokenConfig: Configuration for token validationUsage Pattern:
val tokenValidator = TokenValidator(config)
fun Application.module() {
install(Authentication) {
bearer("azure-ad") {
authenticate { token ->
tokenValidator.validate(token)
}
}
}
routing {
authenticate("azure-ad") {
get("/protected") {
val principal = call.principal<TokenPrincipal>()
call.respond("Hello ${principal.subject}")
}
}
}
}
Dependencies: Ktor auth, Nimbus JOSE JWT
Location: libs:auth-test/main/libs/auth-test/
Main Exports:
JwkGenerator: Generates test JWTsUsage Pattern:
val jwt = JwkGenerator.generateToken(
subject = "test-user",
issuer = "test-issuer",
audience = "test-audience"
)
val client = TestRuntime.httpClient
client.get("/protected") {
bearerAuth(jwt)
}
Location: libs/ws/main/libs/ws/
Main Exports:
SoapClient: Generic SOAP clientSts: STS (Security Token Service) integration for SOAP authUsage Pattern:
val stsClient = Sts(config)
val soapClient = SoapClient(
endpoint = "https://soap.example.com/service",
stsClient = stsClient
)
val request = SimulerBeregningRequest(...)
val response = soapClient.call<SimulerBeregningResponse>(request)
Dependencies: CXF, JAXB
Location: libs/tracing/main/libs/tracing/
Main Exports:
Tracing: OpenTelemetry setuptracer: Global tracer instanceUsage Pattern:
val span = tracer.spanBuilder("operation-name").startSpan()
try {
span.makeCurrent().use {
performOperation()
}
span.setStatus(StatusCode.OK)
} catch (e: Exception) {
span.recordException(e)
span.setStatus(StatusCode.ERROR)
} finally {
span.end()
}
Dependencies: OpenTelemetry SDK
Location: libs/cache/main/libs/cache/
Main Exports:
Cache<T>: Generic cache with expiryTokenCache: Specialized token cacheCacheKey: Key abstractionUsage Pattern:
val cache = Cache<String, Token>(
ttl = 3600.seconds,
loader = { key -> fetchToken(key) }
)
val token = cache.get("azure-ad")
Location: libs/utils/main/libs/utils/
Main Exports:
Result<V, E>: Rust-style Result type with Ok/ErrEnv: Environment variable helpersLog: Logging utilities (appLog, secureLog)Resource: Resource loadingCsvReader: CSV parsingUsage Pattern:
// Result type
val result: Result<Int, String> = Result.catch {
riskyOperation()
}.mapError { e -> e.message ?: "Unknown error" }
result.fold(
onSuccess = { value -> println("Success: $value") },
onFailure = { error -> println("Error: $error") }
)
// Environment
val dbUrl = env("DATABASE_URL") // Throws if missing
val dbUrlOpt = envOrNull("DATABASE_URL") // Returns null if missing
// Logging
appLog.info("Application started")
secureLog.debug("Processing payment for personident: $personident")
fun Application.myApp() {
val datasource = HikariDataSource(hikariConfig)
val tokenValidator = TokenValidator(authConfig)
install(Authentication) {
bearer("azure-ad") {
authenticate { tokenValidator.validate(it) }
}
}
routing {
authenticate("azure-ad") {
get("/data") {
withContext(jdbcCtx) {
val data = MyDao.findAll()
call.respond(data)
}
}
}
}
}
// Kafka Streams does NOT support coroutines
// For transactional guarantees, use DB as coordination layer (see urskog pattern)
val topology = topology("app") {
Topics.input.consume { key, value ->
val result = process(value)
store.put(key, result)
// Separate coroutine-based consumer reads from store and writes to DB
}
}
// utsjekk pattern: sync Kafka status to PostgreSQL for HTTP queries
val topology = topology("app") {
Topics.status.consume { key, statusUpdate ->
statusStore.put(key, statusUpdate)
}
}
routing {
get("/status/{id}") {
withContext(jdbcCtx) {
val status = StatusDao.findById(call.parameters["id"]!!)
call.respond(status ?: HttpStatusCode.NotFound)
}
}
}
launch {
while (true) {
syncStateToDb()
delay(5.seconds)
}
}
object TestRuntime {
val datasource = PostgresContainer.dataSource
val streamsMock = StreamsMock()
val context = datasource.context()
val ktorRuntime = KtorRuntime(Application::myApp)
val httpClient = ktorRuntime.client
}
@Test fun `full integration test`() = runTest(TestRuntime.context) {
TestRuntime.streamsMock.start(createTopology())
val inputTopic = TestTopic(Topics.input)
inputTopic.produce("key1", PaymentRequest(...))
TestRuntime.datasource.await {
MyDao.findById("key1") != null
}
val response = TestRuntime.httpClient.get("/payment/key1")
assertEquals(HttpStatusCode.OK, response.status)
}
Key Testing Utilities:
DataSource.await { }: Polls DB until condition is true (async processing)runTest(TestRuntime.context): Wraps test with correct coroutine contextTestTopic.produce(): Produces to in-memory KafkaStreamsMock.start(): Starts Kafka Streams topology in-memoryWhen adding library dependencies between modules:
dependencies {
implementation(project(":libs:jdbc"))
implementation(project(":libs:kafka"))
implementation(project(":models"))
testImplementation(project(":libs:jdbc-test"))
testImplementation(project(":libs:kafka-test"))
}
Versions are declared inline (no version catalog):
val ktorVersion = "3.0.2"
implementation("io.ktor:ktor-server-core:$ktorVersion")
libs/newlib/main/libs/newlib/ for sourceslibs/newlib/build.gradle.kts with dependenciessettings.gradle.ktsIf creating a production library, consider creating a test companion:
libs:jdbc -> libs:jdbc-testlibs:kafka -> libs:kafka-testlibs:auth -> libs:auth-test