feat(sqlite): initial stable release v0.9.0

Pure-Go CGO-free SQLite client with launcher lifecycle, write-mutex serialisation, health check, unit-of-work via context injection, and structured error mapping.

What's included:
- Executor / Tx / Client / Component interfaces using database/sql native types
- Tx.Commit() / Tx.Rollback() without ctx, matching the honest database/sql contract
- New(logger, cfg) constructor; database opened in OnInit
- Config struct with env-tag support; default Pragmas: WAL + 5s busy timeout + FK enforcement
- PRAGMA foreign_keys = ON enforced explicitly in OnInit
- writeMu sync.Mutex acquired by UnitOfWork.Do to serialise writes and prevent SQLITE_BUSY
- UnitOfWork via context injection; GetExecutor(ctx) returns active Tx or *sql.DB
- HandleError mapping SQLite extended error codes to xerrors codes (unique/primary-key → AlreadyExists, foreign-key → InvalidInput, ErrNoRows → NotFound)
- health.Checkable at LevelCritical; pure-Go modernc.org/sqlite driver (CGO_ENABLED=0 compatible)

Tested-via: todo-api POC integration
Reviewed-against: docs/adr/
This commit is contained in:
2026-03-19 13:25:31 +00:00
commit 237cba9bad
16 changed files with 1053 additions and 0 deletions

38
errors.go Normal file
View File

@@ -0,0 +1,38 @@
package sqlite
import (
"database/sql"
"errors"
"code.nochebuena.dev/go/xerrors"
)
// coder is the duck-type interface for SQLite extended error codes.
// modernc.org/sqlite errors implement this interface.
type coder interface{ Code() int }
const (
sqliteConstraintPrimaryKey = 1555
sqliteConstraintUnique = 2067
sqliteConstraintForeignKey = 787
)
// HandleError maps SQLite and database/sql errors to xerrors types.
func HandleError(err error) error {
if err == nil {
return nil
}
if errors.Is(err, sql.ErrNoRows) {
return xerrors.New(xerrors.ErrNotFound, "record not found").WithError(err)
}
var ce coder
if errors.As(err, &ce) {
switch ce.Code() {
case sqliteConstraintUnique, sqliteConstraintPrimaryKey:
return xerrors.New(xerrors.ErrAlreadyExists, "record already exists").WithError(err)
case sqliteConstraintForeignKey:
return xerrors.New(xerrors.ErrInvalidInput, "data integrity violation").WithError(err)
}
}
return xerrors.New(xerrors.ErrInternal, "unexpected database error").WithError(err)
}