Metadata-Version: 2.3
Name: boro
Version: 0.0.0
Summary: Author and compose Mosaic clients as anywidgets.
Author: Trevor Manz
Author-email: Trevor Manz <trevor.j.manz@gmail.com>
Requires-Dist: anywidget>=0.11.0
Requires-Dist: arro3-io>=0.8.0
Requires-Dist: duckdb>=1.5.2
Requires-Python: >=3.12
Description-Content-Type: text/markdown

# boro

author and compose mosaic clients as anywidgets

## install

```sh
uv add boro
```

## what

[mosaic](https://idl.uw.edu/mosaic/) is an architecture for interactive linked
views over millions of rows: clients publish queries, a coordinator manages and
cross-filters them against a data source (e.g., duckdb).

vgplot is how you usually author custom mosaic-based visualizations, a bundle
with its own grammar and layout. boro is the same client/coordinator
architecture, but each _client_ is a standalone
[anywidget](https://anywidget.dev).

## primitives

three anywidgets:

- **`Coordinator`** — owns a `DataSource` and the js-side mosaic engine. brokers
  queries over arrow ipc. headless.
- **`Selection`** — wraps a mosaic `Selection`. `value` syncs the array of
  clauses (sql predicates) to python.
- **`Client`** — base class. Required `coord`; conventional `filter_by` (read
  side) and `target` (publish side) selection slots, both `boro.SelectionTrait`.
  Subclasses can declare further selection slots with `boro.SelectionTrait()`.

The base accepts a unified `selection=` shortcut covering all four shapes:

```python
Histogram(c, ..., selection=sel)              # crossfilter (both slots = sel)
Histogram(c, ..., selection=(scope, detail))  # asymmetric pair
Histogram(c, ..., selection=(sel, None))      # read-only (filter only)
Histogram(c, ..., selection=(None, sel))      # publish-only
```

Explicit `filter_by=` / `target=` kwargs override the shortcut.

each client is its own `_esm` and DOM. the mosaic engine and js deps live once,
in the coordinator; clients reach it via `host.getWidget(...)`.

## hello world

```python
import boro
import duckdb

con = duckdb.connect()
con.execute("CREATE TABLE flights AS SELECT * FROM read_parquet('flights.parquet')")

c = boro.Coordinator.connect(con)
sel = boro.Selection.crossfilter(c)
```

```py
Histogram(c, table="flights", column="delay", selection=sel)
```

```py
Scatter(c, table="flights", x="distance", y="delay", selection=sel)
```

```python
>>> sel.value
[{'value': [5, 30], 'sql': '"delay" BETWEEN 5 AND 30', 'meta': {...}, 'source': '...'}]
```

## writing a client

```python
import boro
import traitlets


class RowCounter(boro.Client):
    _esm = """
    export default {
      async render({ model, host, signal, el }) {
        el.style.cssText = "font: 14px ui-sans-serif; padding: 6px 8px;";
        el.textContent = "…";
        const coord = await host.getWidget(model.get("coord"));
        const { Query, msql, createClient } = coord.exports;
        const ctx = await createClient({ model, host, signal });
        ctx.liveQuery({
          filterBy: ctx.filterBy,
          query: (filter) =>
            Query.from(model.get("table"))
              .select({ n: msql.count() })
              .where(filter ?? []),
          onResult: (result) => {
            if (!result.isSuccess) {
              return;
            }
            const n = Number(result.data.toColumns().n[0]);
            el.textContent = `${n.toLocaleString()} rows`;
          },
        });
      },
    };
    """
    table = traitlets.Unicode().tag(sync=True)

    def __init__(self, coord: boro.Coordinator, table: str, **kwargs):
        super().__init__(coord=coord, table=table, **kwargs)


RowCounter(c, table="flights", selection=sel)
```

`coord.exports` provides `Query` / `msql` (mosaic-sql), `mc` (mosaic-core), and
`createClient`. `createClient({ model, host, signal })` returns a `ctx` that
exposes:

- **`ctx.selections.<name>`** — resolved mosaic `Selection`s, one per
  `boro.SelectionTrait` field on the Python class (`filter_by` / `target` come
  from the base; subclasses can declare more).
- **`ctx.state(name, opts?)`** — a `{get, set, subscribe}` handle on a sync
  trait. Optional `{target, clause}` opts auto-derive a clause from the trait
  and publish it to a Selection on every change.
- **`ctx.liveQuery({ filterBy }, queryFn, onState)`** — registers a
  filter-driven query. `onState` receives
  `{status: 'idle'|'pending'|'success'|'error', data, error}`.
- **`ctx.coordinator`** — the mosaic `Coordinator` for one-shot ad-hoc queries
  (`await ctx.coordinator.query(sql, { type: 'arrow' })`).
- **`ctx.client`, `ctx.clients`, `ctx.signal`** — the synthetic primary
  `MosaicClient` (clause source identity), the `Set` of all sibling clients used
  for cross-filter exclusion in `state`-derived clauses, and the abort signal
  scoped to this render.

## example

```sh
uv run jupyterlab examples/
```
