This document is read by AI agents when working on this codebase. It describes the current architecture, hard-won decisions, and conventions that must be followed.
This document is updated as the codebase evolves. Treat anything that contradicts the current code as stale — the code is the source of truth.
Use the codegraph MCP as the first tool for browsing code. codegraph_explore answers most questions ("how does X work", "where is Y defined", architecture traces) in a single call — it returns verbatim source grouped by file. Only fall back to Read / grep when codegraph doesn't cover a specific detail or the index hasn't caught a very recent change.
Package: @webiny/data-transfer.
What it does: a generic data-transfer tool for Webiny environments. The flagship use case is v5→v6 migration, but the infrastructure is storage-agnostic and transformer-optional — "copy prod data into dev with zero transformation" is a first-class use case.
Runtime flow (when deployed):
- User writes a single
config.ts:createConfig({ source, target, pipeline }). One file covers DDB, S3, and optional OpenSearch. User-side custom DI:registercallback increateConfig()is the primary path (runs before preset loading).setup.tsnext to the config file is the alternative for larger setups. Both are optional. - CLI:
yarn transferwith no arguments opens a menu over theCommandregistry (src/commands/registry/) — entries:transfer,fix-live.yarn transfer transfer(or the legacyyarn transfer --config … --preset …) runs the system-to-system transfer: without--configtheTransferWizardselects a project, writes.env, then on subsequent runs prompts for a preset and returnsWizardResult { configPath, preset, dryRun }.yarn transfer <folder>still scaffolds (init). Prompts go through thePrompts/UIabstractions (src/commands/prompts/,@clack/prompts); commands never import a prompt library. Cancel exits 130. - Bootstrap loads the config, registers all features (DDB + S3 always; OS conditional on
config.target.opensearch != null), loads the named preset, spawns worker processes per segment. - Each worker runs one or more shards: scans source → for each record, first-match-wins pipeline runs: filters → transformers → each processor's
onEnd?hook (sequential, array order) → commands accumulate in a pending buffer. Everytuning.flushEveryrecords (default 500) each processor'sexecute()drains its own keys from that buffer (sequential, array order) and the buffer resets — this bounds peak memory toflushEvery × avg_record_size. A final flush at shard end drains any remainder.Commands.unclaimedKeys()surfaces commands no processor claimed.
Read before big refactors:
docs/design/generic-pipeline-framework.md— long-term design (pipeline-centric model, merge groups keyed by scanner, first-match-wins).docs/superpowers/specs/2026-04-18-*.md— recent design docs (transformer-library, preset-migration).docs/superpowers/specs/2026-09-04-fix-live-field-and-command-menu-design.md—fix-livereconciler + CLI command menu (what v6 actually maintains forlive, certainty rules, UpdateItem-only writes).
Everything users import lives in src/index.ts: config builder (createConfig), env helpers, AWS credential helpers, transformer/filter factories, scanner/processor classes, preset helper, pipeline construction, context types. Five built-in presets: v5-to-v6-ddb, v5-to-v6-os, copy-ddb, copy-os, copy-files.
Rule: when adding to src/index.ts, it must be something a user building their own transformers/pipelines/presets genuinely needs. Domain-specific migration transformers remain internal.
Full reference: Public API surface
Source lives in src/ with cli.ts entry point, bootstrap.ts DI setup, index.ts public API. CLI commands live in src/commands/ as implementations of the Command token (src/commands/registry/); the entry src/cli.ts registers registry.list() with yargs plus a $0 [folder] default that preserves the two historical no-command invocations. Domain logic is in src/features/ (one dir per feature), pipeline abstractions in src/domain/pipeline/, transform primitives in src/domain/transform/, ~30 built-in transformers in src/transformers/, and 5 built-in presets in src/presets/. Build scripts live in scripts/features/BuildPackages/ (DI-based, mirrors @webiny/stdlib). Build tsconfigs in config/. Changeset config in .changeset/. CI/CD workflows in .github/workflows/.
Full reference: Project structure
DI via @webiny/di — createAbstraction / createImplementation, all processors share one token, constructor identity is the discriminator. Feature layout follows abstractions/ + impl + feature.ts + index.ts. Pipeline runtime: first-match-wins per merge group, onEnd hooks replace auto-put magic, slice-merged context (BaseTransformContext ∧ MergeSlices<TProcessors>).
Full reference: Architecture patterns
Tests in __tests__/ mirror src/. Shared containers in __tests__/containers/, mock clients in __tests__/services/. Integration tests under __tests__/integration/ run against local dynalite. Golden-file preset test in pipeline.preset.test.ts.
Verification before any commit: yarn npm audit, yarn format:fix, yarn ts-check, yarn test:coverage, yarn lint, yarn check:imports. All six required.
Full reference: Testing
Docs in docs/guides/ are the primary documentation for end users installing @webiny/data-transfer via npm. They are NOT internal developer docs — they must be written for users who have never seen the codebase.
Rule: any change to public API, CLI behavior, config shape, preset behavior, or pipeline runtime semantics MUST include corresponding updates to the relevant guide(s). An agent that changes behavior without updating docs has not completed the task.
| Guide | Covers |
|---|---|
config-reference.md |
createConfig shape, env helpers, credentials, IAM, tuning, debug |
commands.md |
CLI commands, flags, wizard flow |
writing-presets.md |
Preset shape, pipeline builder, filters, built-in presets |
writing-transformers.md |
Transformer factories, context types, processor slices, built-ins |
pipeline-customizer.md |
PipelineCustomizer for extending built-in presets |
pipeline-runtime.md |
Merge groups, first-match-wins, unmatched records, hooks, parallelism |
troubleshooting.md |
Common issues and debugging |
These docs also ship in the published npm package and are referenced from the scaffolded project's README.
~30 documented decisions covering: unified createConfig, runtime preset selection, zero-transformer support, record-carries-everything invariant, slice-merging processors, first-match-wins pipeline dispatch, afterShard hook, PipelineCustomizer, per-record ctx.blackhole(), and more.
Full reference: Hard-won decisions
bruno/feat/di-features— slice-merging-processors refactor, afterShard hook, dynalite integration suite, golden-file preset test. Merged tomain.bruno/feat/os-transfer—v5-to-v6-ospreset,OsScanner+OsProcessor,addLiveField, OS transformers,ModelProvidermulti-format JSON. Merged tomain.
Read the latest handoff before starting work: docs/handoff/2026-09-10-fix-live-implementation-and-cli.md. It covers all changes from the 2026-09-04 to 2026-09-10 sessions (fix-live reconciler, CLI command menu, inquirer removal, env/preset/worker fixes) and lists pending follow-up work.
- Fix live field + CLI command menu — implemented. Transformer fix, reconciler, DDB/OS runners, clack-based command menu,
fix-liveguided command, all landed and tested. Spec:docs/superpowers/specs/2026-09-04-fix-live-field-and-command-menu-design.md. - First npm publish — infrastructure is in place (changesets, CI, publish workflow, build scripts). Needs:
NPM_TOKENsecret in GitHub, firstyarn changesetto create a version bump, merge to main. - Init scaffolding smoke —
initscaffolds fromtemplates/. Scaffold output:config.ts,presets/example.ts, optionalsetup.ts. Do a smoke run to verify a scaffolded project compiles + runs against a live sandbox. - End-to-end AWS smoke — no test has ever run against real AWS. Day-long sandbox exercise. Catches real issues mocks can't.
- Public API audit pass (post-refactor) —
src/index.tsgrew organically. Re-audit before publish to confirm the surface matches user-authoring intent.DdbCoreTransformContext(= Base ∧ DdbProcessorSlice) was added as the narrower alternative toDdbTransformContext. - Inquirer removal — done.
TransferWizardmigrated toPrompts/UI;@inquirer/*removed frompackage.json. fix-liveOS propagation — confirm v6's DynamoDB stream handler treats adata-only change on the OS companion table as an index update (spec 2026-09-04, open question 1).
Install: yarn install. Run guided setup: yarn transfer (no --config). Direct run: yarn transfer --config=./path/config.ts --preset=<name>. Build: yarn build. Pack dry-run: yarn pack:packages. Release: yarn release (build + changeset publish). Init user project: npx @webiny/data-transfer my-folder.
Full command reference: Commands Verification commands: Testing — Verification before commit
Persistent user/project memory for agents lives in ~/.claude/projects/.../memory/ and is indexed by MEMORY.md. Key entries:
user_role.md— Bruno, senior Webiny engineer.feedback_*files — house style rules (braces, access modifiers, namespace types, no inline structural types, camelCase file names, no reflect-metadata imports, terse responses, commit per section).feedback_no_transformers_required.md— zero-transformer rule.feedback_keep_ctx_original.md— ctx.original stays.project_*files — project context and open TODOs.
When in doubt about a preference, check MEMORY.md first. When adding a new hard-won decision, save it to a memory file AND surface it in section 6 of this doc.