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
6 changes: 3 additions & 3 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ jobs:
- run: bun install --frozen-lockfile
- name: Check package, types, unit tests and live dialect
env:
EFFECT_CLICKHOUSE_TEST_URL: http://127.0.0.1:8123
EFFECT_CLICKHOUSE_TEST_USER: maple
EFFECT_CLICKHOUSE_TEST_PASSWORD: maple
EFFECT_ORM_CLICKHOUSE_URL: http://127.0.0.1:8123
EFFECT_ORM_CLICKHOUSE_USER: maple
EFFECT_ORM_CLICKHOUSE_PASSWORD: maple
run: bun run test:release
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@

## Unreleased

- Rename the package to `@maple-dev/effect-orm` and the repository to `MapleTechLabs/effect-orm`.
Imports, error `_tag` prefixes (`@maple-dev/effect-orm/QueryBuilderError`, ...) and the live-test
variables (`EFFECT_ORM_CLICKHOUSE_URL`, `_USER`, `_PASSWORD`) change with it.
- Add `Dialect`: how a compiled query writes identifiers and literals, binds params, and
which clauses exist. `compile` and friends take `options.dialect`; `clickhouseDialect` is the
default and its output is unchanged.
Expand Down
30 changes: 16 additions & 14 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
# @maple-dev/effect-clickhouse
# @maple-dev/effect-orm

Type-safe ClickHouse queries, result decoding, and reproducible benchmarks for Effect and TypeScript.
Type-safe ClickHouse and Postgres queries, result decoding, and reproducible benchmarks for Effect and TypeScript.

Formerly `@maple-dev/effect-clickhouse`.

[Read the documentation](https://effect-clickhouse.maple.dev) ·
[Getting started](./docs/getting-started.md) · [Recipes](./docs/recipes.md)
Expand All @@ -27,7 +29,7 @@ Built on [Effect](https://effect.website) (peer dependency).
Install the package with its Effect 4 peer:

```bash
bun add @maple-dev/effect-clickhouse "effect@^4.0.0"
bun add @maple-dev/effect-orm "effect@^4.0.0"
```

See [Getting started](./docs/getting-started.md) for source builds and examples.
Expand All @@ -39,8 +41,8 @@ prereleases are incompatible.
## Quick start

```ts
import * as CH from "@maple-dev/effect-clickhouse"
import * as T from "@maple-dev/effect-clickhouse/types"
import * as CH from "@maple-dev/effect-orm"
import * as T from "@maple-dev/effect-orm/types"

// 1. Describe a table
const Events = CH.table(
Expand Down Expand Up @@ -163,17 +165,17 @@ regressions live in [`src/docs-examples.test.ts`](./src/docs-examples.test.ts).

| Import | Contents |
| ------------------------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `@maple-dev/effect-clickhouse` | Curated public API: `from`, `compile`, `param`, expression helpers, and ClickHouse functions under friendly names (`min`, `max`, `count`, `quantile`, …). |
| `@maple-dev/effect-clickhouse/types` | Column-type constructors (`string`, `uint64`, `dateTime`, `map`, `array`, `nullable`, …) and the `CH*` type descriptors. |
| `@maple-dev/effect-clickhouse/expr` | Kitchen-sink namespace: every expression helper plus all ClickHouse functions under their raw names (`min_`, `toString_`, `toStartOfInterval`, `dynamicColumn`, …). Handy for `import * as CH`. |
| `@maple-dev/effect-clickhouse/sql` | The low-level `SqlFragment` AST (`raw`, `ident`, `compile`, …) for hand-rolling fragments. |
| `@maple-dev/effect-clickhouse/postgres` | The Postgres dialect: `postgresDialect`, Postgres column types and functions, and a `compile` that defaults to Postgres. |
| `@maple-dev/effect-orm` | Curated public API: `from`, `compile`, `param`, expression helpers, and ClickHouse functions under friendly names (`min`, `max`, `count`, `quantile`, …). |
| `@maple-dev/effect-orm/types` | Column-type constructors (`string`, `uint64`, `dateTime`, `map`, `array`, `nullable`, …) and the `CH*` type descriptors. |
| `@maple-dev/effect-orm/expr` | Kitchen-sink namespace: every expression helper plus all ClickHouse functions under their raw names (`min_`, `toString_`, `toStartOfInterval`, `dynamicColumn`, …). Handy for `import * as CH`. |
| `@maple-dev/effect-orm/sql` | The low-level `SqlFragment` AST (`raw`, `ident`, `compile`, …) for hand-rolling fragments. |
| `@maple-dev/effect-orm/postgres` | The Postgres dialect: `postgresDialect`, Postgres column types and functions, and a `compile` that defaults to Postgres. |

## Extending with custom functions

```ts
import type { DateTime } from "effect"
import { defineFn, sameAs } from "@maple-dev/effect-clickhouse"
import { defineFn, sameAs } from "@maple-dev/effect-orm"

// Declare any ClickHouse function not already wrapped. The second argument is
// the ClickHouse type it returns — required, because that is what lets a query
Expand All @@ -195,8 +197,8 @@ run the client example too, with `CLICKHOUSE_URL`, `CLICKHOUSE_USERNAME`, and

Run `bun run build`, `bun run typecheck`, and `bun run test` from this package. Tests include regressions for
nullable results, UNION column alignment, tenant scoping, custom parameters, and DateTime64 precision.
To include the live ClickHouse cases, set `EFFECT_CLICKHOUSE_TEST_URL` and, if needed,
`EFFECT_CLICKHOUSE_TEST_USER` and `EFFECT_CLICKHOUSE_TEST_PASSWORD`. They use only SELECTs and CTEs.
To include the live ClickHouse cases, set `EFFECT_ORM_CLICKHOUSE_URL` and, if needed,
`EFFECT_ORM_CLICKHOUSE_USER` and `EFFECT_ORM_CLICKHOUSE_PASSWORD`. They use only SELECTs and CTEs.

Use `bun run test:release` before publishing: it requires a live endpoint and checks the
build, types, tests, docs, and an isolated tarball consumer. `prepublishOnly` enforces
Expand All @@ -209,7 +211,7 @@ MIT

## Query benchmarks

The optional `@maple-dev/effect-clickhouse/benchmark` entry point and bundled
The optional `@maple-dev/effect-orm/benchmark` entry point and bundled
`ch-bench` CLI measure real queries, compare fixed workloads, and save evidence.
See [Benchmarking](docs/benchmarking.md) and the
[agent playbook](docs/benchmark-agent.md). The root SQL builder remains driver-free.
2 changes: 1 addition & 1 deletion bun.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion design/dialects.md
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ What we deliberately do not copy:
three places in `compile.ts`, which kept ClickHouse output byte-identical without
relocating ~1100 lines. A dialect that needs a structurally different statement is the
point to revisit this.
6. **Postgres dialect (done).** `@maple-dev/effect-clickhouse/postgres`: `postgresDialect`
6. **Postgres dialect (done).** `@maple-dev/effect-orm/postgres`: `postgresDialect`
(double-quoted identifiers, standard strings with `E'…'` only to escape the param marker,
`$n` binding), column types, a function catalog (`count(*)`, `FILTER (WHERE …)`,
`percentile_cont`, `date_trunc(…, 'UTC')`, `date_bin`, `array_agg`, `->>`), and a
Expand Down
20 changes: 10 additions & 10 deletions docs/README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# Effect ClickHouse
# Effect ORM

`@maple-dev/effect-clickhouse` builds ClickHouse SQL from typed TypeScript. You describe a
`@maple-dev/effect-orm` builds ClickHouse SQL from typed TypeScript. You describe a
table once, and the builder infers column types, output row shapes, and join accessors from
it. Queries are immutable values — every method returns a new query — and nothing touches the
network: the end product is a `CompiledQuery` holding a SQL string plus a typed decoder. You
Expand Down Expand Up @@ -65,20 +65,20 @@ Roughly in reading order.

| Import | Contents |
| --------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `@maple-dev/effect-clickhouse` | Curated public API — `from`, `compile`, `param`, expression helpers, and ClickHouse functions under friendly names (`min`, `max`, `count`, `quantile`, …) |
| `@maple-dev/effect-clickhouse/types` | Column-type constructors (`string`, `uint64`, `dateTime`, `map`, `array`, `nullable`, …) and the `CH*` type descriptors |
| `@maple-dev/effect-clickhouse/expr` | Kitchen-sink namespace: every expression helper plus all ClickHouse functions under their raw names (`min_`, `toString_`, `dynamicColumn`, `not`, …) |
| `@maple-dev/effect-clickhouse/sql` | The low-level `SqlFragment` AST (`raw`, `ident`, `compile`, …) for hand-rolling fragments |
| `@maple-dev/effect-clickhouse/benchmark` | Driver-free suite definitions, runner, report schemas, and comparisons |
| `@maple-dev/effect-clickhouse/benchmark/http` | ClickHouse HTTP transport, environment configuration, and query-log collection |
| `@maple-dev/effect-clickhouse/benchmark/cli` | `runCli(args)` for embedding the bundled `ch-bench` commands |
| `@maple-dev/effect-orm` | Curated public API — `from`, `compile`, `param`, expression helpers, and ClickHouse functions under friendly names (`min`, `max`, `count`, `quantile`, …) |
| `@maple-dev/effect-orm/types` | Column-type constructors (`string`, `uint64`, `dateTime`, `map`, `array`, `nullable`, …) and the `CH*` type descriptors |
| `@maple-dev/effect-orm/expr` | Kitchen-sink namespace: every expression helper plus all ClickHouse functions under their raw names (`min_`, `toString_`, `dynamicColumn`, `not`, …) |
| `@maple-dev/effect-orm/sql` | The low-level `SqlFragment` AST (`raw`, `ident`, `compile`, …) for hand-rolling fragments |
| `@maple-dev/effect-orm/benchmark` | Driver-free suite definitions, runner, report schemas, and comparisons |
| `@maple-dev/effect-orm/benchmark/http` | ClickHouse HTTP transport, environment configuration, and query-log collection |
| `@maple-dev/effect-orm/benchmark/cli` | `runCli(args)` for embedding the bundled `ch-bench` commands |

The root barrel is curated, not exhaustive — see
[the reference](./reference.md#whats-only-on-a-subpath) for what lives only on a subpath.

## Query benchmarks

The optional `@maple-dev/effect-clickhouse/benchmark` entry point and bundled
The optional `@maple-dev/effect-orm/benchmark` entry point and bundled
`ch-bench` CLI measure real queries, compare fixed workloads, and save evidence.
See [Benchmarking](./benchmarking.md) and the
[agent playbook](./benchmark-agent.md). The root SQL builder remains driver-free.
12 changes: 6 additions & 6 deletions docs/benchmarking.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# Benchmarking ClickHouse queries

The package ships a driver-free `@maple-dev/effect-clickhouse/benchmark` API,
The package ships a driver-free `@maple-dev/effect-orm/benchmark` API,
an HTTP adapter at `/benchmark/http`, and the `ch-bench` executable. The builder's
root import still does no networking. No Maple account or schema is required.

Expand Down Expand Up @@ -36,9 +36,9 @@ it does not create a dataset or determine whether your query inputs are represen
## Define a workload

```ts title="benchmark-suite.ts"
import { compile, from, param, table } from "@maple-dev/effect-clickhouse"
import { uint32, string } from "@maple-dev/effect-clickhouse/types"
import * as Bench from "@maple-dev/effect-clickhouse/benchmark"
import { compile, from, param, table } from "@maple-dev/effect-orm"
import { uint32, string } from "@maple-dev/effect-orm/types"
import * as Bench from "@maple-dev/effect-orm/benchmark"

const events = table("events", { id: uint32, name: string })
const byName = from(events)
Expand Down Expand Up @@ -178,12 +178,12 @@ Save this beside `benchmark-suite.ts` as `benchmark-runner.ts`, set the same

```ts title="benchmark-runner.ts"
import { Effect } from "effect"
import * as Bench from "@maple-dev/effect-clickhouse/benchmark"
import * as Bench from "@maple-dev/effect-orm/benchmark"
import {
httpConfigFromEnv,
makeHttpClient,
makeHttpTransport,
} from "@maple-dev/effect-clickhouse/benchmark/http"
} from "@maple-dev/effect-orm/benchmark/http"
import suiteDefinition from "./benchmark-suite"

const measurements = await Effect.runPromise(
Expand Down
2 changes: 1 addition & 1 deletion docs/decoding-results.md
Original file line number Diff line number Diff line change
Expand Up @@ -152,7 +152,7 @@ Both decoders fail with `CompiledQueryDecodeError`, carrying the index of the of
```ts
const error = await Effect.runPromise(Effect.flip(compiled.decodeRows([{ name: 42, count: 1 }])))

error._tag // "@maple-dev/effect-clickhouse/CompiledQueryDecodeError"
error._tag // "@maple-dev/effect-orm/CompiledQueryDecodeError"
error.rowIndex // 0
error.message // "Compiled query row 0 did not match its declared output schema"
error.cause // the underlying Schema parse error
Expand Down
4 changes: 2 additions & 2 deletions docs/expressions.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,8 @@ $.Timestamp.gte(new Date(...)) // Timestamp >= '2026-01-01 00:00:00'
`isNull` (or `isNotNull` for present values), declared with `defineCondFn`:

```ts title="null-filter.ts"
import * as CH from "@maple-dev/effect-clickhouse"
import * as T from "@maple-dev/effect-clickhouse/types"
import * as CH from "@maple-dev/effect-orm"
import * as T from "@maple-dev/effect-orm/types"

const Notes = CH.table("notes", { Note: T.nullable(T.string) })
const isNull = CH.defineCondFn<[CH.Expr<string | null>]>("isNull")
Expand Down
12 changes: 6 additions & 6 deletions docs/extending.md
Original file line number Diff line number Diff line change
Expand Up @@ -74,8 +74,8 @@ _(Backed by `docs/extending.md > defineCondFn declares a predicate`.)_
When the signature is too irregular for `defineFn`, write the wrapper yourself:

```ts title="typed-function.ts"
import * as CH from "@maple-dev/effect-clickhouse"
import * as T from "@maple-dev/effect-clickhouse/types"
import * as CH from "@maple-dev/effect-orm"
import * as T from "@maple-dev/effect-orm/types"

const greatestOf = (first: CH.Expr<number>, ...rest: CH.Expr<number>[]) =>
CH.compileTypedFnCall<number>("greatest", T.float64.schema, first, ...rest)
Expand All @@ -101,8 +101,8 @@ For functions whose call syntax is not `fn(a, b)` at all — parametric aggregat
anything bespoke:

```ts
import { makeExpr } from "@maple-dev/effect-clickhouse"
import { raw, compile } from "@maple-dev/effect-clickhouse/sql"
import { makeExpr } from "@maple-dev/effect-orm"
import { raw, compile } from "@maple-dev/effect-orm/sql"

const quantileExact = (q: number) => (expr: CH.Expr<number>) =>
makeExpr<number>(raw(`quantileExact(${q})(${compile(expr.toFragment())})`), T.float64.schema)
Expand All @@ -119,7 +119,7 @@ user-supplied string values through `compile(str(value))` from the `/sql` subpat
produces `[object Object]`. For example:

```ts title="escaped-sql.ts"
import { compile, str } from "@maple-dev/effect-clickhouse/sql"
import { compile, str } from "@maple-dev/effect-orm/sql"

export const predicate = `Name = ${compile(str("O'Reilly"))}`
console.log(predicate) // Name = 'O\'Reilly'
Expand Down Expand Up @@ -231,7 +231,7 @@ _(Backed by `docs/extending.md > rawCompiledQuery wraps handwritten SQL`.)_
The `/sql` subpath exposes the layer everything above is built on:

```ts
import { raw, str, ident, int, join, as_, when, compile } from "@maple-dev/effect-clickhouse/sql"
import { raw, str, ident, int, join, as_, when, compile } from "@maple-dev/effect-orm/sql"
```

- `str(value)` — an escaped string literal. **Use this for anything user-supplied.**
Expand Down
14 changes: 7 additions & 7 deletions docs/getting-started.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ The package declares `effect ^4.0.0` as a peer dependency; Effect 3 is incompati
Install from npm with its Effect 4 peer dependency:

```sh
npm install @maple-dev/effect-clickhouse "effect@^4.0.0"
npm install @maple-dev/effect-orm "effect@^4.0.0"
```

The recommended range accepts stable Effect 4 releases. It excludes Effect 3, the
Expand All @@ -23,8 +23,8 @@ version used to check these examples; it is not an installation pin.
To build from source instead:

```sh
git clone https://github.com/MapleTechLabs/effect-clickhouse.git
cd effect-clickhouse
git clone https://github.com/MapleTechLabs/effect-orm.git
cd effect-orm
bun install --frozen-lockfile
bun run build
bun pm pack
Expand All @@ -48,8 +48,8 @@ wire response; it does not need a server, credentials, or an existing table.

```ts title="quick-start.ts"
import { Effect } from "effect"
import * as CH from "@maple-dev/effect-clickhouse"
import * as T from "@maple-dev/effect-clickhouse/types"
import * as CH from "@maple-dev/effect-orm"
import * as T from "@maple-dev/effect-orm/types"

const Events = CH.table("events", {
Name: T.string,
Expand Down Expand Up @@ -106,8 +106,8 @@ when trying their query snippets. Their table and column names are case-sensitiv
with your own database. Replace them with your real schema before executing.

```ts title="schema.ts"
import * as CH from "@maple-dev/effect-clickhouse"
import * as T from "@maple-dev/effect-clickhouse/types"
import * as CH from "@maple-dev/effect-orm"
import * as T from "@maple-dev/effect-orm/types"

export const Events = CH.table(
"events",
Expand Down
2 changes: 1 addition & 1 deletion docs/joins-and-subqueries.md
Original file line number Diff line number Diff line change
Expand Up @@ -145,7 +145,7 @@ That is what `subqueryExpr` is for. It takes the inner query, the column type it
as, and a `wrap` function that receives the inner SQL and returns the expression text:

```ts
import { subqueryCond, subqueryExpr } from "@maple-dev/effect-clickhouse"
import { subqueryCond, subqueryExpr } from "@maple-dev/effect-orm"

// Stage 1: a cheap scan reading only the sort column.
const cheapScan = CH.from(Events)
Expand Down
8 changes: 4 additions & 4 deletions docs/params-and-compilation.md
Original file line number Diff line number Diff line change
Expand Up @@ -247,7 +247,7 @@ Two classes, and the line between them is what a runtime value can reach.
`QueryBuilderError` describes a **value** the builder was handed and cannot turn into SQL. Code
that assembles a query from a request body can hit every one of these with correct code and bad
input, so `compile` puts them in the Effect error channel, catchable by the tag
`"@maple-dev/effect-clickhouse/QueryBuilderError"`:
`"@maple-dev/effect-orm/QueryBuilderError"`:

| Code | Cause |
| ------------------ | ------------------------------------------------------------------------ |
Expand All @@ -270,8 +270,8 @@ a required parameter and recovers only that typed builder failure; defects are n

```ts title="compile-errors.ts"
import { Effect } from "effect"
import * as CH from "@maple-dev/effect-clickhouse"
import * as T from "@maple-dev/effect-clickhouse/types"
import * as CH from "@maple-dev/effect-orm"
import * as T from "@maple-dev/effect-orm/types"

const Events = CH.table("events", { Name: T.string })
const query = CH.from(Events)
Expand All @@ -281,7 +281,7 @@ const query = CH.from(Events)
export const outcome = await Effect.runPromise(
CH.compile(query, {}).pipe(
Effect.map((compiled) => ({ ok: true as const, sql: compiled.sql })),
Effect.catchTag("@maple-dev/effect-clickhouse/QueryBuilderError", (error) =>
Effect.catchTag("@maple-dev/effect-orm/QueryBuilderError", (error) =>
Effect.succeed({ ok: false as const, code: error.code, message: error.message }),
),
),
Expand Down
Loading
Loading