discussion: client-side document transforms in `elastic es helpers bulk-ingest`
- Dominant language
- TypeScript
- Stars
- 41
- Forks
- 24
- Avg merge
- 1d 3h
- Merged PRs (30d)
- 56
Description
## Summary
`elastic es helpers bulk-ingest` currently passes each parsed document straight through to the bulk API. There is no way to modify, enrich, filter, or split documents on the client side during ingestion. This is more of a **scope / direction question** than a concrete feature request — there are several valid answers, and picking one would unblock the ingest story.
## Current surface
The ingest-pipeline option (`--pipeline `) covers server-side transformation via Elasticsearch ingest processors. That works for grok, geoip, set, rename, script (Painless), etc. — but only for transformations the user can express as an ingest pipeline, and only after the pipeline has been created.
There is no **client-side** transform hook.
## Use cases that don't fit ingest pipelines
1. **Document splitting** — turn one source row into multiple target documents (e.g. split a tweet into one doc per hashtag). Ingest pipelines operate 1:1.
2. **Pre-parse filtering** — drop documents before they hit the wire (`if !doc.email.contains("@") skip`). An ingest pipeline can set `_ingest.drop`, but every dropped doc still pays the network round-trip.
3. **Enrichment from local context** — add `{"ingest_host": "", "batch_id": ""}` to every doc, with the value coming from the local environment rather than the document.
4. **Schema reshape before inference** — flatten / rename / retype fields before `--infer-mappings` sees them, so the inferred mapping matches the target schema.
5. **Secret redaction before send** — strip PII client-side so it never enters ES at all (vs. an ingest pipeline that runs after the doc has reached the cluster).
## Options I see
### A. Pipe through an external program
Document the `cat data.ndjson | jq '...' | elastic es helpers bulk-ingest --input-file -` pattern in `--help`. No CLI code change. Works for most item-1..3 cases as long as the transform fits in `jq`. Doesn't help with formats the external program can't parse (CSV/Parquet), so pairs with #185 but doesn't replace it.
### B. Flag-driven declarative transforms
Add narrow flags for the common cases:
```
--rename-field = # repeatable
--drop-field # repeatable
--add-field = # repeatable
--filter-expr # drop docs where this is true
```
No user scripting, no security surface. Covers ~60% of practical needs. Probably the right MVP.
### C. JS transform plugin
```
--transform
```
Load a user `.js`/`.mjs` file, call its default export as `(doc) => doc | null | doc[]` (the `node-es-transformer` contract). This is what [`elasticsearch-file-ingest`](https://github.com/elastic/agent-skills-sandbox/tree/main/skills/elasticsearch/elasticsearch-file-ingest) currently does via its bundled script. Maximum flexibility but opens a scripting + security surface (untrusted JS execution, module resolution, etc.).
### D. WASM / sandboxed expression language
Like option C but constrained — a small expression language (CEL, Rego, or an embedded Lua/Starlark) that can read/write doc fields without arbitrary code. More work than C, but safer.
## What I'd suggest
- Start with **A + B**: document the external-pipe pattern, and add `--rename-field` / `--drop-field` / `--add-field` for the trivial cases. Ship both in one release. This covers the majority of the file-ingest skill's transform workload without the CLI becoming a scripting runtime.
- Defer C/D unless user demand specifically wants in-process transforms that `jq` / external programs can't express.
If the team has an opinion here, capturing it on this issue lets skill authors stop maintaining their own transform runtimes ([`elasticsearch-file-ingest`](https://github.com/elastic/agent-skills-sandbox/tree/main/skills/elasticsearch/elasticsearch-file-ingest) is the immediate case — see [sandbox PR #359](https://github.com/elastic/agent-skills-sandbox/pull/359)).
## Relationship to other issues
- [#185](https://github.com/elastic/cli/issues/185) asks for CSV/Parquet/Arrow parsing in `bulk-ingest`. Independent of this issue — parsing is about format, transforms are about content.
- [#184](https://github.com/elastic/cli/issues/184) (text-structure) is a one-shot mapping + pipeline inference — not a streaming transform.
## Environment
- CLI: `0.1.0-alpha.1`
- Surfaced during: https://github.com/elastic/agent-skills-sandbox elasticsearch skills migration (PR [#359](https://github.com/elastic/agent-skills-sandbox/pull/359))
Contributor guide
Assessment
This issue has not been assessed yet.