Skip to main content

Sluice ORM

Type-safe MongoDB aggregation pipeline builder

Write MongoDB aggregation pipelines where the return type is inferred from the query — not from a manual generic annotation.

🔬

Full Type Inference

Every $group, $project, and $addFields stage produces a precisely inferred output type. Chain 10 stages and the final type is exactly what MongoDB returns — no casts, no generics, no guessing.

🔗

Pipeline Type Flow

Each stage's output becomes the next stage's input. Rename a field in $project? The next $group immediately sees the new shape. Typos are caught before your code ever runs.

Zero Runtime Cost

All type checking happens at compile time. Your production bundle contains only the MongoDB queries you wrote — no wrappers, no reflection, no overhead.

🧩

Schema Agnostic

Works with Effect Schema, Zod, or plain TypeScript types. Bring your own validation library, or skip runtime validation entirely.

🗄️

Full MongoDB 8.0+

Every aggregation stage, expression operator, accumulator, and window function — typed. $facet, $setWindowFields,$lookup with sub-pipelines, all covered.

🎭

Effect Integration

Optional first-class Effect.ts support. Get tagged errors, dependency injection, and composable pipelines for production applications.

Types Flow Through Your Pipeline

Each stage transforms the document shape. Sluice infers the output type at every step — no manual annotations needed.

pipeline-type-flow.ts
import { registry, $match, $group, $project, $sort } from "sluice-orm";
import { Schema as S } from "@effect/schema";

const OrderSchema = S.Struct({
_id: S.String,
userId: S.String,
amount: S.Number,
items: S.Array(S.Struct({
name: S.String,
price: S.Number,
quantity: S.Number,
})),
status: S.Literal("pending", "paid", "shipped"),
createdAt: S.Date,
});

const db = registry("8.0", { orders: OrderSchema });
const { orders } = db(client.db("shop"));

// Every stage's output type flows to the next — fully inferred
const report = await orders
.aggregate(
$match($ => ({ status: "paid" })),
// ^? { _id: string; userId: string; amount: number; ... }

$group($ => ({
_id: "$userId",
totalSpent: $.sum("$amount"),
orderCount: $.sum(1),
})),
// ^? { _id: string; totalSpent: number; orderCount: number }

$project($ => ({
userId: "$_id",
totalSpent: 1,
orderCount: 1,
avgOrder: $.divide("$totalSpent", "$orderCount"),
_id: 0,
})),
// ^? { userId: string; totalSpent: number; orderCount: number; avgOrder: number }

$sort({ totalSpent: -1 }),
)
.toList();

// report: { userId: string; totalSpent: number; orderCount: number; avgOrder: number }[]

Without Sluice

// No type safety — runtime errors waiting to happen
collection.aggregate([
{ $group: { _id: "$departement", avg: { $avg: "$salray" } } },
// ^ typo ^ typo
// You won't know until production 💥
]);

With Sluice

// Compile-time safety — errors caught instantly
$group($ => ({
_id: "$departement", // ❌ TS Error: no field "departement"
avg: $.avg("$salray"), // ❌ TS Error: no field "salray"
}));
// Fix the typos, ship with confidence ✅