# Local data apps

A local data app is a running dcupl SDK instance — a **daemon** — that lives on your machine. Ingest data into it, build it, then query, facet, and aggregate straight from your terminal. It's the fastest way to profile a CSV, prototype a model, or explore a dataset before you commit to Console.

## Lifecycle

The flow is always the same: **create** a daemon, **mutate** data (which stages changes), **build** to apply them — or skip the build with `--auto-update` — then **query**.

### create

Start a daemon (an in-memory SDK instance)

### mutate

Ingest data — stages changes

### build

Apply staged changes (or use --auto-update)

### query

Query, facet & aggregate — then destroy

## Worked example: profile a CSV

Spin up a daemon, load a CSV, and profile it end to end. The `--json` flag makes every command emit machine-readable output you can pipe into other tools.

```console title="zsh — profiling products.csv"
# start an interactive daemon that applies mutations on the fly
$ dcupl app create --auto-update --json
{"appId": "happy-lynx-4d5b","port": 35169}

# ingest the CSV, inferring the model from a 100-row sample
$ dcupl app data upsert --file products.csv --model Product \
  --key-property id --auto-generate-sample-size 100 --json
{"upserted": 12,"model": "Product","built":true}

# see the most common categories (--sort size-desc makes --limit a top-N by count)
$ dcupl app fn facets --model Product --attribute category \
  --sort size-desc --limit 10 --json
[
  {"value": "Accessories","count": 5},
  {"value": "Outerwear","count": 4},
  {"value": "Footwear","count": 3}
]

# get price statistics
$ dcupl app fn aggregate --model Product --attribute price --types avg,min,max --json
{"attribute": "price","types":["avg","min","max"],"avg": 158.65333333333334,"min": 4.99,"max": 489}

# top 5 most expensive products over $100
$ dcupl app query execute --model Product \
  --query '{"attribute":"price","operator":"gt","value":100}' \
  --sort price:desc --limit 5 --projection productName,price --json
[
  {"key": "SKU-1190","productName": "Alpine Shell","price": 489},
  {"key": "SKU-0042","productName": "Trail Parka","price": 412.5},
  {"key": "SKU-0871","productName": "Storm Boots","price": 318},
  {"key": "SKU-0015","productName": "Approach Shoes","price": 159},
  {"key": "SKU-0010","productName": "Rain Jacket","price": 149}
]

# clean up
$ dcupl app destroy --json
{"destroyed": "happy-lynx-4d5b"}
```

> **Watch out:** **Numeric types from CSV.** CSV cells arrive as strings; when dcupl infers the model it types integer, decimal and ISO-date columns (here `price` becomes `float`). A digit string counts as a number only when the number keeps every character, so zero-padded codes like `00123` stay `string`, and so does a column that mixes them with plain numbers. `--auto-generate-properties` alone takes the column names and types from the **first row**, then checks every column it typed as a number against the other rows: a later `4.99` in a column that starts with `318` makes it `float`, a later `00123` makes it `string`. Other columns keep the first row's type. Reconcile every column with `--auto-generate-deep` (every row) or `--auto-generate-sample-size` (the first *n* rows); `dcupl generate model --from` always does, over every row or the first `--sample-size` rows. A zero-padded value you want as a number, such as a month `01`–`12`, is inferred as `string` — for those, and whenever you need exact types, define the model with `dcupl app models set` before ingesting. On a daemon without `--auto-update`, run `dcupl app build` right after `models set`: when one build applies both the model and the data, the model is lost and queries fail with `MODEL_NOT_FOUND`.

## Manage the daemon

When you run a single app, the `--app <id>` flag is optional — the CLI targets the active one. With multiple apps, pass `--app` to disambiguate.

### Daemon management

| Command | What it does |
| --- | --- |
| `dcupl app create [--auto-update]` | Start a daemon and register an app |
| `dcupl app list` | List running apps |
| `dcupl app status [--app <id>]` | Show app state, including pending staged changes |
| `dcupl app build [--app <id>]` | Run update() and apply all staged changes |
| `dcupl app logs [--app <id>] [--lines <n>]` | Tail the daemon logs |
| `dcupl app destroy [--app <id>] [--keep-logs]` | Shut down the daemon |

### Create flags

The flags most relevant to the daemon workflow. See Commands for the full reference.

| Flag | Description |
| --- | --- |
| `--auto-update` | Apply every mutation immediately, no build step needed |
| `--auto-generate-properties` | Infer a model from data on first ingest |
| `--auto-generate-deep` | Walk every row when inferring and reconcile types (instead of the first row plus a number check) |
| `--auto-generate-sample-size <n>` | Scan at most the first n rows when inferring a model |
| `--quality <bool>` | Run data quality checks (default true) |
| `--logging-level <level>` | silent, error, warn, info, or debug |
| `--idle-timeout <min>` | Auto-shutdown after this many idle minutes (default 30) |

### Ingest verbs

Use dcupl app data <verb> to feed data in. Each verb maps to an SDK mutation.

| Verb | Effect |
| --- | --- |
| `upsert` | Add or update items |
| `update` | Update existing items; rows with an unknown key are skipped, and the reply counts updated and skipped rows (with up to 20 skippedKeys). Use upsert to add items. Before the first build of a model inferred with --key-property, pass the flag again: without it the reply counts the rows as skipped, although the build applies them |
| `set` | Replace all items for a model |
| `remove` | Remove items by key |
| `reset` | Drop all items for a model |

### Input flags

| Flag | Description |
| --- | --- |
| `--file <path>` | Read data from a file |
| `--url <uri>` | Read data from a URL |
| `--content <str>` | Inline content. --content=- reads stdin (the spaced form --content - is rejected) |
| `--format <fmt>` | csv, json, or ndjson |
| `--model <str>` | Target model |
| `--key-property <str>` | Key property. Comma-separate for a composite key: a,b builds the key "a::b" (the keyProperty array of the SDK) |
| `--auto-generate-key` | Generate a random key for items that have none |

### Query flags

| Flag | Description |
| --- | --- |
| `--model <str>` | Model to query |
| `--query <str>` | Filter expression (the filter flag is --query) |
| `--query-file <path>` | Read the filter from a file instead |
| `--sort name:asc` | Sort spec, e.g. price:desc |
| `--limit <n>` | Maximum number of results |
| `--start <n>` | Offset for pagination |
| `--projection <str>` | Comma-separated list of attributes to return |

## Query & analytics

Fetch one item by key or run a filtered, sorted, paginated query — then layer the SDK's analytics functions (`dcupl app fn`) over the same data.

```bash filename="query + analytics"
# Get one item by key
dcupl app query one --model Product --item-key SKU-0042

# Run a filtered, sorted, paginated query
dcupl app query execute \
  --model Product \
  --query '{"attribute":"price","operator":"gt","value":100}' \
  --sort price:desc --limit 5 --projection productName,price,category

# Analytics: facets, aggregate, groupBy, metadata
dcupl app fn facets    --model Product --attribute category --sort size-desc
dcupl app fn aggregate --model Product --attribute price --types avg,min,max
dcupl app fn groupBy   --model Product --attribute category --aggregate price:avg
dcupl app fn metadata  --model Product
```

> **Note:** The filter flag is `--query`, not `--filter`. It takes dcupl query JSON: one condition (`{"attribute","operator","value"}`), an array of conditions (combined with AND), or a full query group with a `queries` array. MongoDB-style filters such as `{"price":{"$gt":100}}` are rejected. For complex filters, keep the expression in a file and pass `--query-file`.

## Loader integration

To query data sourced through a loader configuration, start the app with `--load`. This registers, processes, and builds a loader so the app is queryable from workspace files or remote sources. In a local workspace the loader reads the files through `dcupl serve`: run it in another terminal, or pass `--auto-serve` to start one for the app (without either, `--load` fails with `SERVE_UNREACHABLE`). Remote sources need credentials — see [Cloud Sync](/cli/cloud-sync).

```bash
dcupl app create --load --auto-serve
```

## Next steps

- [Cloud Sync](/cli/cloud-sync) — Configure credentials and sync files
- [Validate](/cli/validate) — Check your models and data for issues
- [Scaffolding](/cli/scaffolding) — Scaffold projects and generate models
- [Commands](/cli/commands) — The complete CLI command reference
