.md

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.

01

create

Start a daemon (an in-memory SDK instance)

02

mutate

Ingest data — stages changes

03

build

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

04

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.

zsh — profiling products.csv

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"}

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 listList 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-updateApply every mutation immediately, no build step needed
--auto-generate-propertiesInfer a model from data on first ingest
--auto-generate-deepWalk 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
upsertAdd or update items
updateUpdate 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
setReplace all items for a model
removeRemove items by key
resetDrop 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-keyGenerate 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:ascSort 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.

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.

dcupl app create --load --auto-serve