Android JNI example

libUasmUniZstd.so exports both the existing Node-API module and an Android JNI API. Copy or source-set-link android/Zstd.kt into the Android application and package the matching ABI's libUasmUniZstd.so under jniLibs.

Initialize the Android Node-API runtime before the first reference to Zstd. This combined library still imports the host's napi_* symbols. If Kotlin must also work before or without that runtime, build jni_binding.cc as a separate JNI shared library and link both bridges to the same static codec library.

val compressed = Zstd.compress("hello from Kotlin")
val restored = Zstd.decompress(compressed).toString(Charsets.UTF_8)

val binaryCompressed = Zstd.compress(byteArrayOf(1, 2, 3), level = 10)
val parallelCompressed = Zstd.compress(largeInput, level = 10, workers = 2)
val binaryRestored = Zstd.decompress(
    binaryCompressed,
    maxOutputSize = 16L * 1024L * 1024L,
)

To avoid copying native output into a ByteArray, use the direct-buffer API. The returned buffer refers to native memory and must only be used inside the owner's use block:

Zstd.compressDirect(largeInput).use { output ->
    val buffer: ByteBuffer = output.buffer
    channel.write(buffer)
}

ZstdDecompressor.updateDirect() and the other Direct variants use the same ownership rule. Calling close() releases the native output; any retained ByteBuffer is invalid afterwards. Input is still a ByteArray, so this API only removes the native-to-Kotlin output copy.

For files or other large inputs, read fixed-size chunks and write each returned chunk immediately:

ZstdCompressor(workers = 2).use { compressor ->
    source.inputStream().use { input ->
        destination.outputStream().use { output ->
            val chunk = ByteArray(64 * 1024)
            while (true) {
                val count = input.read(chunk)
                if (count < 0) break
                output.write(compressor.update(chunk.copyOf(count)))
            }
            output.write(compressor.finish())
        }
    }
}

Use ZstdDecompressor the same way for decompression. Its maxOutputSize limit applies to the sum of all update results. finish() validates that the final compressed frame is complete.

workers = 0 is the default single-threaded mode. A positive value configures that many zstd compression workers; decompression remains single-threaded.

This mirrors the JavaScript API:

const compressed = zstd.compress('hello from JavaScript')
const restored = zstd.decompress(compressed).toString('utf8')

The Kotlin package and object name are part of the static JNI symbol names. If uts.sdk.modules.zstd.Zstd is renamed, update both Java_... function names in jni_binding.cc, or switch the bridge to RegisterNatives.