# TinyStore 0.1.1

TinyStore 0.1.1 came out on 24 October 2026, four days after the first
release. Rust programs can await every engine, metrics reads a Prometheus
exporter's text, and a database's batch can add a job together with its
rows. A store's logger can print less than it keeps, and a slow query stops
when its caller gives up.

- **Rust**
  - [Await every engine](#await-every-engine): `tinystore::aio` runs on any executor.
- **Metrics**
  - [Read a Prometheus exporter](#read-a-prometheus-exporter): one parser in the core for every SDK.
- **SQL**
  - [A row and its job in one batch](#a-row-and-its-job-in-one-batch): `queue.adding` commits with the statements.
  - [Stop a slow query](#stop-a-slow-query): cancel it and get its connection back.
- **Records**
  - [Print less than you keep](#print-less-than-you-keep): a level for the store and one for the console.
- **Config**
  - [Where each setting came from](#where-each-setting-came-from): every value it was given, as data.

> \[!WARNING]
> **0.1.0 can't open a store after 0.1.1**
> 0.1.1 migrates `records.db` when it first opens, so that events of one
> time keep the order they were appended in. After that, 0.1.0 refuses the
> file. See [Changed names](#changed-names) for the rest.

## Await every engine

The `async` feature adds `tinystore::aio`. Each call that reads, writes or
waits returns a future, and TinyStore brings no runtime of its own, so it
runs on tokio, async-std or smol:

```rust
use tinystore::aio::Store;

let store = Store::open("data", Options::default())?;
let stock = store.bucket::<i64>("stock").open()?;
stock.set("sku-1", &10).await?;
```

A read stops when you drop its future, and a write runs on the store's own
threads, never on the thread that polls it. Bun, Node and Python were
already awaited, and Go blocks a goroutine, so this part is Rust only.
[Rust with async](https://tinyshed.org/tinystore/docs/handbook/start/rust-async) has the details.

## Read a Prometheus exporter

`ingestPrometheus` reads the text that an exporter such as node_exporter
serves, and writes all of its series in one call:

```ts
const text = await (await fetch('http://web-1:9100/metrics')).text()
await store.metrics.ingestPrometheus(text, { labels: { instance: 'web-1' } })
```

```python
text = httpx.get("http://web-1:9100/metrics").text
await store.metrics.ingest_prometheus(text, labels={"instance": "web-1"})
```

```go
resp, err := http.Get("http://web-1:9100/metrics")
body, err := io.ReadAll(resp.Body)
err = metrics.IngestPrometheus(ctx, store, body, metrics.Labels{"instance": "web-1"})
```

```rust
let stats = store.metrics()?;
stats.prometheus().label("instance", "web-1").ingest(&body)?;
```

It reads Prometheus' text format and OpenMetrics, and a store now holds
more than 100,000 series when it opens with `max_series` (`maxSeries`,
`MaxSeries`).

## A row and its job in one batch

A queue that you open from a database keeps its jobs in the database's file.
Its `adding` gives a job that a batch writes together with the statements:

```ts
const indexing = db.queue<{ noteId: number }>('indexing')

await db.batch([
	sql`update notes set body = ${body} where id = ${noteId}`,
	indexing.adding({ noteId }), // written with the row, or not at all
])
```

```python
indexing = db.queue("indexing", Index)

await db.batch([
    sql("update notes set body = ? where id = ?", body, note_id),
    indexing.adding(Index(note_id=note_id)),  # written with the row, or not at all
])
```

```go
indexing, err := jobs.OpenQueue[Index](db, "indexing")

err = db.Batch(ctx,
	sqldb.SQL(`update notes set body = ? where id = ?`, body, noteID),
	indexing.Adding(Index{NoteID: noteID}), // written with the row, or not at all
)
```

```rust
let indexing = db.queue::<Index>("indexing").open()?;

db.batch([
    sql!("update notes set body = ? where id = ?", body, note_id).into(),
    indexing.adding(&Index { note_id }), // written with the row, or not at all
])?;
```

A worker sees the job only after the commit, so it never reads a row that
isn't there yet.

## Print less than you keep

A store's logger takes a level for each place: `store` is the least level
kept, and `console` the least level printed. The terminal shows warnings,
and a query still finds every line from `info`:

```ts
const log = store.logger('api', { level: { store: 'info', console: 'warn' } })
```

```python
log = store.logger("api", level={"store": "info", "console": "warning"})
```

```go
handler := records.NewHandler(store, "api", records.Levels{Store: slog.LevelInfo, Console: slog.LevelWarn})
```

```rust
let levels = Levels { store: Some(Level::INFO), console: Some(Level::WARN) };
let log = store.logger("api").level(levels).layer()?;
```

`LOG_CONSOLE_LEVEL` sets the console's part from the environment, beside
`LOG_LEVEL`.

## Where each setting came from

Each line of `explain` now has `given`: every value the setting was given,
newest first, each with where it came from as data. Use it to show a
setting's history in your own words:

```ts
const rps = config.explain().find(line => line.path === 'limits.rps')
const [own, under] = rps?.given ?? []
```

```python
rps = next(line for line in config.explain() if line.path == "limits.rps")
own, under = rps.given[0], rps.given[1]
```

```go
own, under := line.Given[0], line.Given[1] // line: the explain line of limits.rps
```

```rust
let explained = config.explain();
let rps = explained.settings.iter().find(|line| line.path == "limits.rps").unwrap();
```

[Explain](https://tinyshed.org/tinystore/docs/guides/config/explain#each-value-as-data) shows what each
origin holds.

## Stop a slow query

When a visitor closes a tab or a request passes its timeout, cancel the
query. TinyStore stops its SQLite statement at once and gives the
connection back:

```ts
const signal = AbortSignal.any([request.signal, AbortSignal.timeout(2000)])
const totals = await withSignal(signal, () => db.all`select user_id, sum(total) from orders group by user_id`)
```

```python
async with asyncio.timeout(2):
    totals = await db.all("select user_id, sum(total) from orders group by user_id")
```

```go
ctx, cancel := context.WithTimeout(r.Context(), 2*time.Second)
defer cancel()
totals, err := sqldb.All[Total](ctx, db, `select user_id, sum(total) from orders group by user_id`)
```

```rust
let token = CancellationToken::timeout(Duration::from_secs(2));
let totals: Vec<Total> = token.run(|| db.all("select user_id, sum(total) from orders group by user_id"))?;
```

The same works for the reads of metrics and records.
[Stopping a slow query](https://tinyshed.org/tinystore/docs/guides/sql/cancel) has the details.

## Changed names

- `records.db` moves to a new format at the first open, and 0.1.0 refuses
  the file afterwards.
- Each `explain` line gains `given`. In Rust, `config::Line`, `Explained`
  and the new `Origin` are `#[non_exhaustive]`. In Go, `Line` holds a slice
  and can no longer be compared with `==`.
- Python's `store.log_handler(level=...)` sets the store logger's level, and
  a level that TinyStore doesn't know, such as `'CRITICAL'`, is refused.
- In Bun and Node, a field whose value is `undefined` is left out of a log
  line instead of written as `"undefined"`.
- A store's directory that is a file is an invalid error in every SDK, and a
  cancelled call in Bun and Node throws `CancelledError` instead of the
  signal's `DOMException`.

## Fixes

- A connection that is lost while a handle opens sends the write again on
  the next connection, instead of answering that its outcome is unknown.
- A store's close cancels its subscriptions, loops, tails and watches in
  Bun, Node and Python, which kept a child process busy for ten seconds.
- A Windows server's secret and named pipe are readable by its own user
  only.
- Go's `uint64` and `float32` values in KV read back the same in Rust.
- Nodes and their copies got eight fixes to leases, links and fencing.

## Install

```sh for=bun
bun add @tinyshed/tinystore@0.1.1
```

```sh for=python
pip install tinyshed-tinystore==0.1.1
```

```sh for=go
go get tinyshed.org/tinystore@v0.1.1
```

```sh for=rust
cargo add tinystore@0.1.1
```

Every commit since 0.1.0 is in the
[GitHub release](https://github.com/tinyshed/tinystore/releases/tag/v0.1.1).
