소스 정보
- 저장소
- navikt/helved-utbetaling
- 최근 소스 활동
- 2026년 4월 28일 08:35
- 감지된 SKILL.md 언어
- 영어
- 스타
- 1
- 포크
- 1
설치 방법
기본적으로 소스를 먼저 확인하는 Prompt가 선택됩니다. 직접 명령으로 전환하거나 로컬 사본을 다운로드할 수도 있습니다.
소스 파일 검토
설치 여부를 결정하기 전에 SKILL.md와 SkillsMP에 표시된 보조 파일을 읽어 보세요.
메뉴
기본적으로 소스를 먼저 확인하는 Prompt가 선택됩니다. 직접 명령으로 전환하거나 로컬 사본을 다운로드할 수도 있습니다.
설치 여부를 결정하기 전에 SKILL.md와 SkillsMP에 표시된 보조 파일을 읽어 보세요.
Codex 또는 Claude로 설치 이 Prompt를 복사해 Codex, Claude 또는 다른 어시스턴트에 붙여 넣으면 Skill 페이지를 검토하고 설치를 진행할 수 있습니다.
직접 명령은 검토 Prompt를 거치지 않습니다. 실행하기 전에 소스를 확인하세요.
npx skills add https://github.com/navikt/helved-utbetaling --skill libs-reference명령은 한 줄로 유지됩니다. 복사하기 전에 가로로 스크롤해 전체 내용을 확인하세요.
로컬 사본을 원하시나요? SkillsMP에서 현재 제공할 수 있는 파일을 다운로드하세요.
SKILL.md 표시 중
| 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-testSOC 직업 분류 기준