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.
start an interactive daemon that applies mutations on the fly: dcupl app create --auto-update --json — output: {"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 — output: {"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 — output: [ {"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 — output: {"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 — output: [ {"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 — output: {"destroyed":"happy-lynx-4d5b"}
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.
# 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 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.
dcupl app create --load --auto-serve