Skip to content
Open
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
68 changes: 68 additions & 0 deletions .changeset/auto-register-operators.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
---
"@tanstack/db": patch
---

Refactor operators and aggregates to embed their evaluators directly in IR nodes for true tree-shaking support and custom extensibility.

Each operator and aggregate now bundles its builder function and evaluator factory in a single file. The factory is embedded directly in the `Func` or `Aggregate` IR node, eliminating the need for a global registry. This enables:

- **True tree-shaking**: Only operators/aggregates you import are included in your bundle
- **No global registry**: No side-effect imports needed; each node is self-contained
- **Custom operators**: Create custom operators by building `Func` nodes with a factory
- **Custom aggregates**: Create custom aggregates by building `Aggregate` nodes with a config

**Custom Operator Example:**

```typescript
import {
Func,
type EvaluatorFactory,
type CompiledExpression,
} from "@tanstack/db"
import { toExpression } from "@tanstack/db/query"

const betweenFactory: EvaluatorFactory = (compiledArgs, _isSingleRow) => {
const [valueEval, minEval, maxEval] = compiledArgs
return (data) => {
const value = valueEval!(data)
return value >= minEval!(data) && value <= maxEval!(data)
}
}

function between(value: any, min: any, max: any) {
return new Func(
"between",
[toExpression(value), toExpression(min), toExpression(max)],
betweenFactory
)
}
```

**Custom Aggregate Example:**

```typescript
import {
Aggregate,
type AggregateConfig,
type ValueExtractor,
} from "@tanstack/db"
import { toExpression } from "@tanstack/db/query"

const productConfig: AggregateConfig = {
factory: (valueExtractor: ValueExtractor) => ({
preMap: valueExtractor,
reduce: (values) => {
let product = 1
for (const [value, multiplicity] of values) {
for (let i = 0; i < multiplicity; i++) product *= value
}
return product
},
}),
valueTransform: "numeric",
}

function product<T>(arg: T): Aggregate<number> {
return new Aggregate("product", [toExpression(arg)], productConfig)
}
```
25 changes: 25 additions & 0 deletions packages/db/src/query/builder/aggregates/avg.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import { groupByOperators } from "@tanstack/db-ivm"
import { Aggregate } from "../../ir.js"
import { toExpression } from "../ref-proxy.js"
import type { AggregateReturnType, ExpressionLike } from "../operators/types.js"

// ============================================================
// CONFIG
// ============================================================

const avgConfig = {
factory: groupByOperators.avg,
valueTransform: `numeric` as const,
}

// ============================================================
// BUILDER FUNCTION
// ============================================================

export function avg<T extends ExpressionLike>(arg: T): AggregateReturnType<T> {
return new Aggregate(
`avg`,
[toExpression(arg)],
avgConfig
) as AggregateReturnType<T>
}
21 changes: 21 additions & 0 deletions packages/db/src/query/builder/aggregates/count.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import { groupByOperators } from "@tanstack/db-ivm"
import { Aggregate } from "../../ir.js"
import { toExpression } from "../ref-proxy.js"
import type { ExpressionLike } from "../operators/types.js"

// ============================================================
// CONFIG
// ============================================================

const countConfig = {
factory: groupByOperators.count,
valueTransform: `raw` as const,
}

// ============================================================
// BUILDER FUNCTION
// ============================================================

export function count(arg: ExpressionLike): Aggregate<number> {
return new Aggregate(`count`, [toExpression(arg)], countConfig)
}
8 changes: 8 additions & 0 deletions packages/db/src/query/builder/aggregates/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
// Re-export all aggregates
// Importing from here will auto-register all aggregate evaluators

export { sum } from "./sum.js"
export { count } from "./count.js"
export { avg } from "./avg.js"
export { min } from "./min.js"
export { max } from "./max.js"
25 changes: 25 additions & 0 deletions packages/db/src/query/builder/aggregates/max.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import { groupByOperators } from "@tanstack/db-ivm"
import { Aggregate } from "../../ir.js"
import { toExpression } from "../ref-proxy.js"
import type { AggregateReturnType, ExpressionLike } from "../operators/types.js"

// ============================================================
// CONFIG
// ============================================================

const maxConfig = {
factory: groupByOperators.max,
valueTransform: `numericOrDate` as const,
}

// ============================================================
// BUILDER FUNCTION
// ============================================================

export function max<T extends ExpressionLike>(arg: T): AggregateReturnType<T> {
return new Aggregate(
`max`,
[toExpression(arg)],
maxConfig
) as AggregateReturnType<T>
}
25 changes: 25 additions & 0 deletions packages/db/src/query/builder/aggregates/min.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import { groupByOperators } from "@tanstack/db-ivm"
import { Aggregate } from "../../ir.js"
import { toExpression } from "../ref-proxy.js"
import type { AggregateReturnType, ExpressionLike } from "../operators/types.js"

// ============================================================
// CONFIG
// ============================================================

const minConfig = {
factory: groupByOperators.min,
valueTransform: `numericOrDate` as const,
}

// ============================================================
// BUILDER FUNCTION
// ============================================================

export function min<T extends ExpressionLike>(arg: T): AggregateReturnType<T> {
return new Aggregate(
`min`,
[toExpression(arg)],
minConfig
) as AggregateReturnType<T>
}
25 changes: 25 additions & 0 deletions packages/db/src/query/builder/aggregates/sum.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import { groupByOperators } from "@tanstack/db-ivm"
import { Aggregate } from "../../ir.js"
import { toExpression } from "../ref-proxy.js"
import type { AggregateReturnType, ExpressionLike } from "../operators/types.js"

// ============================================================
// CONFIG
// ============================================================

const sumConfig = {
factory: groupByOperators.sum,
valueTransform: `numeric` as const,
}

// ============================================================
// BUILDER FUNCTION
// ============================================================

export function sum<T extends ExpressionLike>(arg: T): AggregateReturnType<T> {
return new Aggregate(
`sum`,
[toExpression(arg)],
sumConfig
) as AggregateReturnType<T>
}
Loading
Loading