The features matter because the proof loop is fast, visible, and repeatable.
A loop that ends with a green check and real output files.
Commands and artifacts stand in for slides and opinions.
SQL and contracts run anywhere, so adoption feels safe.
.deet and templates keep every demo consistent.
A powerful type system that infers types from your data sources and propagates them through every transformation. No more runtime surprises.
Types are automatically inferred from your data sources. No need for manual annotations.
Type information flows through every transformation, catching errors instantly.
All type errors are caught before deployment, not at 3am in production.
Explicit nullable types (string?) and compile-time null checks eliminate the most common source of data bugs. Filter once, type-safe forever.
-- This will fail at runtime if email is NULL
SELECT
id,
UPPER(email) as email_upper,
LENGTH(email) as email_length
FROM users
-- ERROR: null value in column "email"
-- Query failed after processing 1.2M rows<span class="text-deet-orange font-semibold">model</span> user_emails = users
<span class="text-deet-green font-semibold">|></span> <span class="text-cyan-400">filter</span>(email != <span class="text-deet-orange font-semibold">null</span>) <span class=<span class="text-amber-400">"text-slate-500 italic"</span>>// Now email: <span class="text-purple-400">string</span></span>
<span class="text-deet-green font-semibold">|></span> <span class="text-cyan-400">derive</span>(
email_upper: <span class="text-cyan-400">upper</span>(email), <span class=<span class="text-amber-400">"text-slate-500 italic"</span>>// Safe!</span>
email_length: length(email) <span class=<span class="text-amber-400">"text-slate-500 italic"</span>>// Safe!</span>
)
<span class=<span class="text-amber-400">"text-slate-500 italic"</span>>// Compiler guarantees: no <span class="text-deet-orange font-semibold">null</span> errors possible</span>1<span class="text-deet-orange font-semibold">source</span> users: {2id: <span class="text-purple-400">i64</span>3email: <span class="text-purple-400">string</span>? <span class=<span class="text-amber-400">"text-slate-500 italic"</span>>// Nullable</span>4}56<span class=<span class="text-amber-400">"text-slate-500 italic"</span>>// email is <span class="text-purple-400">string</span>? here</span>7users.email <span class=<span class="text-amber-400">"text-slate-500 italic"</span>>// Type: <span class="text-purple-400">string</span>?</span>
1<span class="text-deet-orange font-semibold">model</span> active_users = users2<span class="text-deet-green font-semibold">|></span> <span class="text-cyan-400">filter</span>(email != <span class="text-deet-orange font-semibold">null</span>)34<span class=<span class="text-amber-400">"text-slate-500 italic"</span>>// email is now <span class="text-purple-400">string</span>!</span>5active_users.email <span class=<span class="text-amber-400">"text-slate-500 italic"</span>>// Type: <span class="text-purple-400">string</span></span>
Write your transformations once, compile to DuckDB, PostgreSQL, BigQuery, or Snowflake. Switch backends without changing a line of code.
1<span class="text-deet-orange font-semibold">module</span> analytics23<span class="text-deet-orange font-semibold">source</span> users = <span class="text-cyan-400">table</span>(<span class="text-amber-400">"users"</span>) : {4id: <span class="text-purple-400">i64</span>5name: <span class="text-purple-400">string</span>6email: <span class="text-purple-400">string</span>?7created_at: <span class="text-purple-400">timestamp</span>8}910<span class="text-deet-orange font-semibold">model</span> active_users = users11<span class="text-deet-green font-semibold">|></span> <span class="text-cyan-400">filter</span>(email != <span class="text-deet-orange font-semibold">null</span>)12<span class="text-deet-green font-semibold">|></span> <span class="text-cyan-400">derive</span>(days_active: datediff(<span class="text-amber-400">'day'</span>, created_at, <span class="text-cyan-400">now</span>()))13<span class="text-deet-green font-semibold">|></span> <span class="text-cyan-400">filter</span>(days_active > 0)1415<span class=<span class="text-amber-400">"text-slate-500 italic"</span>>// Single <span class="text-deet-orange font-semibold">source</span> of truth</span>16<span class=<span class="text-amber-400">"text-slate-500 italic"</span>>// Compiles to any backend</span>
-- Generated for DuckDB
SELECT
u.id,
u.name,
COALESCE(u.email, '') AS email,
DATE_DIFF('day', u.created_at, NOW()) AS days_active
FROM users u
WHERE u.email IS NOT NULL
AND DATE_DIFF('day', u.created_at, NOW()) > 0Full Language Server Protocol support brings autocomplete, go-to-definition, hover types, and inline errors to VS Code, Neovim, and more.
Context-aware suggestions for functions, columns, and models
See type errors and warnings as you type, not after
Jump to model definitions with a single click
See inferred types by hovering over any expression
Works with your favorite editor
Built-in assertions and contracts validate your data at every stage. Test your transformations like you test your code.
1<span class="text-deet-orange font-semibold">module</span> tests23<span class="text-deet-orange font-semibold">import</span> analytics45<span class=<span class="text-amber-400">"text-slate-500 italic"</span>>// Inline assertions</span>6<span class="text-deet-orange font-semibold">model</span> validated_revenue = analytics.revenue7<span class="text-deet-green font-semibold">|></span> <span class="text-deet-orange font-semibold">assert</span>(total_revenue >= 0, <span class="text-amber-400">"Revenue must be positive"</span>)8<span class="text-deet-green font-semibold">|></span> <span class="text-deet-orange font-semibold">assert</span>(order_count > 0, <span class="text-amber-400">"Must have orders"</span>)9<span class="text-deet-green font-semibold">|></span> <span class="text-deet-orange font-semibold">expect</span>(avg_order_value < 10000, <span class="text-amber-400">"Flag unusual values"</span>)1011<span class=<span class="text-amber-400">"text-slate-500 italic"</span>>// Data contracts</span>12<span class="text-deet-orange font-semibold">test</span> revenue_has_required_fields =13analytics.revenue14<span class="text-deet-green font-semibold">|></span> <span class="text-cyan-400">limit</span>(1)15<span class="text-deet-green font-semibold">|></span> <span class="text-deet-orange font-semibold">assert</span>(customer_id is <span class="text-deet-orange font-semibold">not</span> <span class="text-deet-orange font-semibold">null</span>, <span class="text-amber-400">"customer_id required"</span>)16<span class="text-deet-green font-semibold">|></span> <span class="text-deet-orange font-semibold">assert</span>(order_date is <span class="text-deet-orange font-semibold">not</span> <span class="text-deet-orange font-semibold">null</span>, <span class="text-amber-400">"order_date required"</span>)17<span class="text-deet-green font-semibold">|></span> <span class="text-deet-orange font-semibold">assert</span>(total_revenue is <span class="text-deet-orange font-semibold">not</span> <span class="text-deet-orange font-semibold">null</span>, <span class="text-amber-400">"total_revenue required"</span>)1819<span class="text-deet-orange font-semibold">test</span> revenue_is_consistent =20analytics.revenue21<span class="text-deet-green font-semibold">|></span> <span class="text-cyan-400">group</span>(<span class="text-deet-orange font-semibold">by</span>: [order_date], measures: { daily_total = <span class="text-cyan-400">sum</span>(total_revenue) })22<span class="text-deet-green font-semibold">|></span> <span class="text-deet-orange font-semibold">assert</span>(daily_total >= 0, <span class="text-amber-400">"Daily total must be non-negative"</span>)
Interactive column-level lineage tracking shows exactly how data moves through your pipeline. Debug with confidence, refactor with clarity.
deet automatically tracks how data flows through your models, from source tables to final outputs. See exactly which columns depend on which sources, and understand the impact of any change before you make it.
1<span class=<span class="text-amber-400">"text-slate-500 italic"</span>>// Query lineage programmatically</span>2let deps = lineage(revenue_report)34<span class=<span class="text-amber-400">"text-slate-500 italic"</span>>// What tables does this <span class="text-deet-orange font-semibold">model</span> depend <span class="text-deet-orange font-semibold">on</span>?</span>5deps.sources <span class=<span class="text-amber-400">"text-slate-500 italic"</span>>// [<span class="text-amber-400">"users"</span>, <span class="text-amber-400">"orders"</span>, <span class="text-amber-400">"products"</span>]</span>67<span class=<span class="text-amber-400">"text-slate-500 italic"</span>>// What columns flow into <span class="text-amber-400">'total_revenue'</span>?</span>8deps.column(<span class="text-amber-400">"total_revenue"</span>).upstream9<span class=<span class="text-amber-400">"text-slate-500 italic"</span>>// [<span class="text-amber-400">"orders.amount"</span>, <span class="text-amber-400">"orders.quantity"</span>, <span class="text-amber-400">"products.price"</span>]</span>1011<span class=<span class="text-amber-400">"text-slate-500 italic"</span>>// What would break <span class="text-deet-orange font-semibold">if</span> we remove <span class="text-amber-400">'users.email'</span>?</span>12deps.impact(<span class="text-amber-400">"users.email"</span>)13<span class=<span class="text-amber-400">"text-slate-500 italic"</span>>// [<span class="text-amber-400">"user_orders.email"</span>, <span class="text-amber-400">"revenue_report.customer_email"</span>]</span>
Try deet in our interactive playground - no signup required. See the type system, null safety, and multi-backend compilation in action.