Skip to content

Inference rules and registration validation

addPipeline(...) infers pipeline behavior from the shape of source and target.

"Replicate", "materialize", and "move-to-service" are documentation use-case labels — they name three common combinations of read shape and target destination, but they are not a field on addPipeline(...) and not a column on the tracker.

Inference rules

Read shape

ConfigRead shapeInferred defaults
source.query presentQuery-shape — single-shot read of the closure's SELECT CQN result.mode: 'full', refresh: 'full'
source.entity (or rest.path) present, source.query absentEntity-shape — paginated readStream(tracker) via the source adapter.mode: 'full' (no delta config). Add mode: 'delta' for incremental sync with default delta.mode: 'timestamp', delta.field: 'modifiedAt'.
Both presentError — ambiguous source shape.
Neither presentError — missing source shape.

Target adapter selection

ConfigTarget adapter
target.adapter: MyTargetAdapter (class ref)The supplied class is used; target.service is ignored for dispatch.
target.service unset or 'db'DbTargetAdapter — writes via cds.connect.to('db').
target.service set to any other value with no target.adapterError — no built-in adapter for non-db services. See Custom target adapter.

Registration validation matrix

addPipeline rejects incoherent configs at registration time. Every row is grounded in a concrete conflict between config and adapter capability.

#Config conflictResponse
1source.query AND source.entity (or rest.path) both setError — ambiguous source shape
2neither source.query nor source.entity / rest.pathError — missing source shape
3source.query + mode: 'delta'Error — row-delta requires entity-shape source; use 'full' or 'partial-refresh'
4source.query + delta.mode: 'timestamp' | 'key' | 'datetime-fields'Error — timestamp / key / datetime-delta requires entity-shape source
5mode: 'partial-refresh' (or refresh: { mode: 'partial' }) without refresh.sliceError — slice closure required for partial-refresh
6mode: 'delta' + target adapter lacks keyAddressableUpsertError — target cannot UPSERT per key; use 'full' or pick a different target
7mode: 'full' + target adapter lacks truncate and batchDeleteError — target cannot clear the slice for full refresh
8source.query + target adapter lacks batchInsertError — target cannot accept snapshot writes
9source.origin + source.queryError — materialize (query-shape) rebuilds the snapshot and is origin-agnostic
10source.origin + target entity missing key source : String (aspect plugin.data_pipeline.sourced)Error — stamp has nowhere to land; import the aspect from cds-data-pipeline/db

Rows 6–8 are evaluated against the capabilities() reported by the resolved target adapter.

Rows 9–10 cover the multi-source fan-in rules. See the Multi-source recipe for the end-to-end pattern.

Multi-source (origin stamp)

source.origin is orthogonal to the inferred pipeline kind: it is a bare label that the default MAP / WRITE handlers stamp into the target's source key column. It composes with the entity-shape cases (replicate, move-to-service) and is rejected against query-shape (materialize) because snapshot rebuilds ignore per-row discriminators.

Read shapesource.origin setOutcome
Entity-shape (replicate / move-to-service)yesDefault MAP writes record.source = origin; UPSERT uses the compound key (businessKey, source); flush and mode: 'full' scope their DELETE to source = <origin>
Entity-shapenoSingle-origin behaviour — no stamp, full-table truncate on flush / mode: 'full'
Query-shape (materialize)yesRejected at registration (row 9 above)
Query-shapenoStandard materialize — snapshot rebuilds the whole target per refresh scope

The target entity must mix in the plugin.data_pipeline.sourced aspect (or declare key source : String(N) directly) whenever any pipeline writes an origin into it; this is row 10 of the validation matrix. See Multi-source fan-in for the aspect import, association-source extension, and per-origin flush assertion.

Examples

Entity-shape replicate to local DB

javascript
await pipelines.addPipeline({
    name: 'OrdersCopy',
    source: { service: 'reporting', entity: 'reporting.Orders' },
    target: { entity: 'db.ArchivedOrders' },
    mode: 'delta',
    delta: { mode: 'timestamp', field: 'modifiedAt' },
});
// Inferred: entity-shape, mode=delta(timestamp modifiedAt), target adapter=DbTargetAdapter.

Query-shape materialize

javascript
await pipelines.addPipeline({
    name: 'DailyCustomerRevenue',
    source: {
        kind: 'cqn',
        service: 'SalesService',
        query: () => SELECT.from('SalesService.Orders')
            .columns('customer_id',
                { func: 'sum', args: [{ ref: ['amount'] }], as: 'total' })
            .groupBy('customer_id'),
    },
    target: { entity: 'reporting.DailyCustomerRevenue' },
});
// Inferred: mode=full, refresh=full, target adapter=DbTargetAdapter.

Entity-shape to a custom target adapter

javascript
const ReportingTargetAdapter = require('./adapters/ReportingTargetAdapter');

await pipelines.addPipeline({
    name: 'OrdersToReporting',
    source: { service: 'OrdersService', entity: 'Orders' },
    target: {
        service: 'ReportingService',
        entity: 'ReportingService.OrderFacts',
        adapter: ReportingTargetAdapter,
    },
});
// Inferred: mode=full, target adapter=ReportingTargetAdapter. The custom
// adapter's capabilities() decides which modes are accepted.

See Custom target adapter for how to implement ReportingTargetAdapter.

Observability

At registration the plugin logs one line per pipeline so the inference is visible in the startup log:

[cds-data-pipeline] registered 'OrdersCopy' — entity-shape from reporting.reporting.Orders → db.db.ArchivedOrders, mode=full

The line names the read shape, source and target references, and the effective mode + delta mode.

When source.origin is set, the line gains a trailing , origin=<label> so multi-source setups are self-documenting:

[cds-data-pipeline] registered 'BP_DEV' — entity-shape from API_BP_DEV.A_BusinessPartner → db.BusinessPartners, mode=delta(timestamp modifiedAt), origin=DEV

See also

Released under the MIT License.