Skip to content

kkokotero/VectorLite

Repository files navigation

VectorLite

VectorLite banner

GitHub badge Version badge MIT license badge Android badge Kotlin badge JitPack Version

VectorLite is an Android library for local relational data, CRUD services, reactive table changes, and vector search — all on-device.

It is designed for apps that need:

  • clean entity mapping
  • simple CRUD access
  • custom service layers
  • query DSLs
  • vector embeddings
  • relationships between tables
  • transactions
  • backup and restore
  • SQLite extensions

Installation

repositories {
    maven("https://jitpack.io")
}

Add the dependency

dependencies {
    implementation("com.github.kkokotero:VectorLite:1.0.0")
}

Why VectorLite

Most Android apps end up splitting persistence into several layers:

Database
↓
Repository
↓
Service
↓
Business logic
↓
Vector search integration

VectorLite keeps that flow in one library:

VectorLite
├── Entities
├── CRUD
├── Repositories
├── Services
├── Relations
├── Vector search
├── Triggers
└── Backup / restore

Quick start

1) Define entities

import io.github.kkokotero.vectorlite.orm.Column
import io.github.kkokotero.vectorlite.orm.DataTable
import io.github.kkokotero.vectorlite.orm.ForeignKey
import io.github.kkokotero.vectorlite.orm.Relationship
import io.github.kkokotero.vectorlite.orm.RelationshipType
import io.github.kkokotero.vectorlite.orm.VectorColumn

@DataTable("users")
data class UserEntity(
    @Column(primaryKey = true, autoIncrement = true, nullable = false)
    val id: Long = 0,

    @Column(nullable = false, unique = true)
    val email: String,

    @Column(nullable = false)
    val name: String
)

@DataTable("face_embeddings")
data class FaceEmbeddingEntity(
    @Column(primaryKey = true, autoIncrement = true, nullable = false)
    val id: Long = 0,

    @ForeignKey(entity = UserEntity::class)
    @Column(nullable = false)
    val userId: Long,

    @VectorColumn(dimensions = 512, elementSize = 4)
    val embedding: FloatArray
)

2) Create the database

If you do not set a name, VectorLite uses the app label by default.

import io.github.kkokotero.vectorlite.VectorLite

val db = VectorLite.database(context) {
    entities(
        UserEntity::class,
        FaceEmbeddingEntity::class
    )

    services(
        FaceRecognitionService::class
    )
}

3) Use the API

val users = db.crud<UserEntity>()
val embeddings = db.repository<FaceEmbeddingEntity>()
val faceService = db.service<FaceRecognitionService>()

CRUD

Use crud<T>() when you want a simple table-oriented service.

val users = db.crud<UserEntity>()

users.create(
    UserEntity(name = "Kevin", email = "kevin@example.com")
)

val kevin = users.first {
    UserEntity::email equal "kevin@example.com"
}

val allUsers = users.findAll()

If you want lower-level access and batch helpers, use repository<T>().

val embeddings = db.repository<FaceEmbeddingEntity>()

embeddings.insertAll(
    FaceEmbeddingEntity(userId = 1, embedding = queryVector),
    FaceEmbeddingEntity(userId = 2, embedding = otherVector)
)

Custom services

Custom services are the recommended place for business logic.

import io.github.kkokotero.vectorlite.DefaultTableService
import io.github.kkokotero.vectorlite.Repository
import io.github.kkokotero.vectorlite.Service
import io.github.kkokotero.vectorlite.VectorLiteSession
import io.github.kkokotero.vectorlite.orm.Table

@Service
class FaceRecognitionService(
    table: Table<FaceEmbeddingEntity>,
    session: VectorLiteSession,
    private val embeddings: Repository<FaceEmbeddingEntity>
) : DefaultTableService<FaceEmbeddingEntity>(table, session) {

    fun storeEmbedding(userId: Long, vector: FloatArray): FaceEmbeddingEntity {
        return create(
            FaceEmbeddingEntity(
                userId = userId,
                embedding = vector
            )
        )
    }
}

Usage:

val service = db.service<FaceRecognitionService>()
service.storeEmbedding(userId = 1, vector = embedding)

Query DSL

val users = db.crud<UserEntity>()

val kevin = users.first {
    UserEntity::name equal "Kevin"
}

val matchedUsers = users.where {
    UserEntity::email matches "%@example.com"
}

Available operators:

  • equal
  • notEqual
  • greaterThan
  • greaterThanOrEqual
  • lessThan
  • lessThanOrEqual
  • matches
  • inside

Vector search

val results = db.repository<FaceEmbeddingEntity>()
    .query()
    .nearestTo(
        column = FaceEmbeddingEntity::embedding,
        vector = queryVector,
        options = io.github.kkokotero.vectorlite.orm.VectorSearchOptions(
            topK = 5,
            approximate = true
        )
    )
    .vectorSearch()

val bestMatch = results.bestMatch

If you only need the closest result:

val match = db.table<FaceEmbeddingEntity>().nearestNeighbor(
    vectorColumn = FaceEmbeddingEntity::embedding,
    queryVector = queryVector
)

Relationships

@DataTable("users")
data class UserEntity(
    @Column(primaryKey = true, autoIncrement = true, nullable = false)
    val id: Long = 0,

    @Column(nullable = false)
    val name: String,

    @Relationship(
        targetEntity = FaceEmbeddingEntity::class,
        type = RelationshipType.ONE_TO_MANY,
        mappedBy = "userId"
    )
    var embeddings: List<FaceEmbeddingEntity> = emptyList()
)

Relationships can be loaded automatically through the query API.


Transactions

db.transaction {
    val users = db.repository<UserEntity>()
    val embeddings = db.repository<FaceEmbeddingEntity>()

    val userId = users.insert(
        UserEntity(name = "Kevin", email = "kevin@example.com")
    )

    embeddings.insert(
        FaceEmbeddingEntity(
            userId = userId,
            embedding = queryVector
        )
    )
}

Batch helpers such as insertAll() and upsertAll() already run inside a transaction.


Reactive changes and triggers

db.tableChanges("users").collect { event ->
    println("${event.tableName} changed: ${event.operation}")
}

Typed triggers:

db.triggers.afterInsert<UserEntity> {
    println("User inserted")
}

db.triggers.afterUpdate<UserEntity> {
    println("User updated")
}

db.triggers.afterDelete<UserEntity> {
    println("User deleted")
}

Backup and restore

import java.io.File

db.exportTo(File(context.filesDir, "vectorlite-backup.db"))
db.importFrom(File(context.filesDir, "vectorlite-backup.db"))

Project structure

app/
├── data/
│   ├── entities/
│   ├── services/
│   └── database/
└── ui/

For a public library, keep the demo app focused on real usage and keep the library code small and readable.


Good fit

  • face recognition
  • semantic search
  • local AI memory
  • offline-first apps
  • embedded catalogs
  • relationship-heavy data models

Comparisons

VectorLite vs Room

Feature Room VectorLite
Relational data
Type-safe Kotlin API
Built-in CRUD service layer Partial
Vector search No
Reactive table changes Limited
SQLite extensions Limited
On-device AI workloads No

VectorLite vs sqlite-vector

Feature sqlite-vector VectorLite
Vector search
ORM No
Relations No
Services No
Query DSL No
Android-friendly API Low-level High-level

VectorLite vs ObjectBox

Feature ObjectBox VectorLite
Local database
Vector search
SQLite based No
SQL access Limited
Custom SQLite extensions No
Service layer Manual Built-in

Requirements

  • Android-only
  • minSdk 27
  • Kotlin 17
  • AndroidX bundled SQLite

Status

VectorLite is under active development.

The API is intended to stay clean and public-facing, but it may still evolve before the first stable release.


Contributing

See CONTRIBUTING.md.

Code of conduct

See CODE_OF_CONDUCT.md.

License

See LICENSE.

About

VectorLite is an Android-first local data platform that combines relational storage, vector search, automatic services and offline-first architecture on top of SQLite, enabling developers to build AI-powered applications entirely on-device.

Topics

Resources

License

Code of conduct

Contributing

Security policy

Stars

Watchers

Forks

Packages

 
 
 

Contributors