Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -21,10 +21,12 @@ jobs:
for i in $(seq 1 60); do
if curl -sSf http://localhost:8123/ >/dev/null 2>&1; then
echo "ClickHouse is ready"
break
exit 0
fi
sleep 2
done
echo "ClickHouse did not become ready in time"
exit 1

- name: Setup Erlang/OTP and Gleam
uses: erlef/setup-beam@v1
Expand Down
125 changes: 0 additions & 125 deletions .github/workflows/release.yml

This file was deleted.

65 changes: 0 additions & 65 deletions .github/workflows/smoke-integration.yml

This file was deleted.

18 changes: 12 additions & 6 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,18 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [Unreleased]

### Added
## [1.1.0] - 2026-03-20

### Changed

- (Unreleased changes go here)
- Simplified and refactored `types.gleam`, `expr.gleam`, `format/registry.gleam`, `format/json_each_row.gleam`, `repo.gleam`, `retry.gleam`, `schema.gleam`, `changeset.gleam`, `decode.gleam`, `query.gleam` — reduced surface area, improved type safety, removed redundant code
- Rewrote `docs/quickstart.md` with correct, runnable code examples
- Added missing documentation for all core modules: `query`, `repo`, `encode`, `decode`, `changeset`, `types`, `schema`
- Removed `release.yml` and `smoke-integration.yml` workflows; CI consolidated under `ci.yml`
- Fixed stray character and incorrect function call in README

## [0.1.0] - 2025-11-09
## [1.0.0] - 2025-11-09

### Added

Expand Down Expand Up @@ -60,5 +65,6 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Style guide for contributions

<!-- Link references for versions -->
[Unreleased]: https://github.com/lupodevelop/sparkling/compare/v0.1.0...HEAD
[0.1.0]: https://github.com/lupodevelop/sparkling/releases/tag/v0.1.0
[Unreleased]: https://github.com/lupodevelop/sparkling/compare/v1.1.0...HEAD
[1.1.0]: https://github.com/lupodevelop/sparkling/compare/v1.0.0...v1.1.0
[1.0.0]: https://github.com/lupodevelop/sparkling/compare/v0.1.0...v1.0.0
6 changes: 3 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
[![CI](https://github.com/lupodevelop/sparkling/actions/workflows/ci.yml/badge.svg)](https://github.com/lupodevelop/sparkling/actions/workflows/ci.yml) [![Hex](https://img.shields.io/hexpm/v/sparkling.svg)](https://hex.pm/packages/sparkling) [![License](https://img.shields.io/badge/license-Apache%202.0-yellow.svg)](LICENSE) [![Built with Gleam](https://img.shields.io/badge/Built%20with-Gleam-ffaff3)](https://gleam.run) [![Gleam Version](https://img.shields.io/badge/gleam-%3E%3D1.13.0-ffaff3)](https://gleam.run)

**Sparkling** is a *lightweight*, **type-safe** data layer for **ClickHouse** written in Gleam. It provides a small, focused API for defining schemas, building queries, and encoding/decoding ClickHouse formats.
É

*No magic*, just small, composable functions that play nicely in Gleam apps.

> Why "Sparkling"? One rainy Tuesday a tiny inflatable rubber duck stole a shooting pink star and decided to become a freelance data wrangler, and it now guides queries through the night, humming 8-bit lullabies. Totally plausible.
Expand All @@ -24,7 +24,7 @@ import sparkling/repo
let r = repo.new("http://localhost:8123")
|> repo.with_database("mydb")

case r.execute_sql(r, "SELECT 1 as result FORMAT JSONEachRow") {
case repo.execute_sql(r, "SELECT 1 as result FORMAT JSONEachRow") {
Ok(body) -> io.println(body)
Error(_) -> io.println("query failed")
}
Expand All @@ -38,7 +38,7 @@ case r.execute_sql(r, "SELECT 1 as result FORMAT JSONEachRow") {
- `sparkling/encode` / `sparkling/decode` — format handlers (JSONEachRow default)
- `sparkling/types` — helpers for Decimal, DateTime64, UUID, LowCardinality

For more examples see `docs/examples/` and `docs/quickstart.md`.
For more examples see [docs/quickstart.md](docs/quickstart.md) and [docs/examples/](docs/examples/).

**Design note:** Sparkling's API and composable query builder were partly inspired by *Ecto*;
many ideas about schema definition and query composition borrow from its approach while keeping a small, Gleam-friendly surface.
Expand Down
82 changes: 82 additions & 0 deletions docs/examples/changeset.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
# Changeset

`sparkling/changeset` provides Ecto-style data casting and validation.

## Basic usage

```gleam
import gleam/option.{None, Some}
import sparkling/changeset

type UserData {
UserData(name: String, email: String, age: Int)
}

let data = UserData(name: "", email: "", age: 0)

let cs =
changeset.new(data)
|> changeset.put_change("name", "Alice")
|> changeset.put_change("email", "alice@example.com")
|> changeset.put_change("age", "28")
|> changeset.validate_required("name")
|> changeset.validate_required("email")
|> changeset.validate_email("email")
|> changeset.validate_length("name", Some(2), Some(100))
|> changeset.validate_number("age", Some(0), Some(120))

case changeset.apply(cs) {
Ok(_) -> io.println("valid!")
Error(errors) -> io.println(changeset.format_errors(errors))
}
```

## Validators

```gleam
// Required — field must be present in changes
|> changeset.validate_required("name")

// Length — min/max for string fields
|> changeset.validate_length("name", Some(2), Some(100))
|> changeset.validate_length("bio", None, Some(500)) // max only

// Number — min/max for int fields (value parsed from change string)
|> changeset.validate_number("age", Some(0), Some(120))

// Email format
|> changeset.validate_email("email")

// Not empty string
|> changeset.validate_not_empty("name")

// Custom format check
|> changeset.validate_format("slug", fn(v) {
string.all(v, fn(c) { c == "-" || c >= "a" && c <= "z" || c >= "0" && c <= "9" })
}, "must contain only lowercase letters, digits, and hyphens")
```

## Reading changes and errors

```gleam
changeset.is_valid(cs) // => True / False
changeset.get_changes(cs) // => Dict(String, String)
changeset.get_errors(cs) // => List(FieldError)

case changeset.get_change(cs, "name") {
Ok(name) -> io.println("name is: " <> name)
Error(Nil) -> io.println("name not set")
}
```

## Manual errors

```gleam
let cs =
changeset.new(data)
|> changeset.put_change("email", "alice@example.com")
|> changeset.add_error("email", "already taken")

changeset.format_errors(changeset.get_errors(cs))
// => "email: already taken"
```
Loading
Loading