# Time-Window Export Mode

## When to use

Use `mode: time_window` to export only rows within a rolling N-day window from the current timestamp. Best for:

- Event tables where you only need the last 7/30/90 days
- Periodic refresh of a "recent activity" dataset
- When `incremental` is not suitable because you need overlapping windows

## Required fields

- `time_column` -- the timestamp column to filter on
- `days_window` -- how many days back from now to include

## Minimal config

```yaml
source:
  type: postgres
  url: "postgresql://user:pass@host:5432/dbname"

exports:
  - name: recent_events
    query: "SELECT id, user_id, event_type, payload, created_at FROM events"
    mode: time_window
    time_column: created_at         # timestamp column to filter
    days_window: 30                 # include rows from the last 30 days
    format: parquet
    destination:
      type: local
      path: ./output
```

## Run it

```bash
rivet check --config events.yaml
rivet run --config events.yaml --validate
```

## What happens

1. Rivet calculates the cutoff: `NOW() - 30 days`
2. Appends `WHERE created_at >= '2026-03-07 00:00:00'` to your query
3. Exports all matching rows as a fresh file
4. No cursor is stored -- each run re-evaluates the window

Unlike `incremental`, this mode produces overlapping data across runs (the last 30 days always overlap with yesterday's last 30 days).

### Re-runs always emit a new file (intentional)

`time_window` does not persist "we already exported this window" anywhere.
Each `rivet run` re-evaluates the rolling window relative to `NOW()` and
writes a fresh file. Two back-to-back runs inside the same minute will
produce two near-identical files — same rows, different `run_id` /
timestamp suffix. That is the contract: this mode is built for *rolling
recent activity* (alerting, daily syncs), not for *exactly-once
delivery*. If you need the latter, switch to `mode: incremental` with a
`cursor_column` and `skip_empty: true`.

Downstream consumers that need deduplication should key off the
`id` / business key inside the rows themselves, not on file name or
`run_id`.

## Time column types

By default, Rivet assumes a `TIMESTAMP`/`DATETIME` column. For Unix epoch integers, set `time_column_type`:

```yaml
exports:
  - name: recent_events
    query: "SELECT id, user_id, event_type, created_at_epoch FROM events"
    mode: time_window
    time_column: created_at_epoch
    time_column_type: unix          # column stores Unix epoch (seconds)
    days_window: 7
    format: csv
    destination:
      type: local
      path: ./output
```

| `time_column_type` | Column type | Filter generated |
|-------------------|-------------|-----------------|
| `timestamp` (default) | `TIMESTAMP` / `DATETIME` | `WHERE col >= '2026-03-07 00:00:00'` |
| `unix` | `INT` / `BIGINT` | `WHERE col >= 1741305600` |

## Common options

```yaml
exports:
  - name: recent_events
    query: "SELECT id, user_id, event_type, created_at FROM events"
    mode: time_window
    time_column: created_at
    days_window: 30
    format: parquet
    compression: zstd
    skip_empty: true
    destination:
      type: local
      path: ./output
```

## Troubleshooting

**0 rows exported but the table has data** -- Check that `days_window` is large enough. Events older than the window are excluded. Also check timezones.

**Duplicates across runs** -- This is by design. Each run exports the full window. Downstream consumers should deduplicate by primary key.

**Need non-overlapping exports** -- Use `mode: incremental` with `cursor_column` instead.
