SAP BTP – APIM – DDCR – Deterministic Routing Mechanism JavaScript Policy full explanation each step

Estimated read time 72 min read

How a Single JavaScript Policy Routes 39 Tested SAP Integration Paths Across 4 Domains Without Hardcoded Business Routes

Zero regex. Zero split. Zero hardcoded business routes. The KVM defines the route.

This post explains the GDCR Routing Engine — Phantom v12, a JavaScript policy used inside SAP BTP API Management to resolve semantic inbound URLs into deterministic SAP CPI target URLs.

This is not a universal plug-and-play router for every enterprise URL structure. It is a reference template: a compact, production-oriented base that can be adapted by companies according to their own URL depth, KVM structure, naming convention, routing granularity, and performance requirements.

The important distinction is this:

Phantom v12 is stable as a reference implementation, but it is not 100% automatically adaptable to every company landscape without adjustment.

If a company changes the URL grammar, KVM key format, route metadata model, adapter rules, or output headers, the script must be adapted. That is normal. The value of Phantom v12 is not that it magically fits every architecture. The value is that it gives a clear, deterministic, metadata-driven routing template that avoids hardcoded business endpoints inside the proxy.

 

1. Where This Script Lives in SDIA

Before opening the code, it is important to understand where this JavaScript policy sits.

SDIA — Semantic Domain Integration Architecture organizes the integration landscape around domain-owned artifacts and metadata-driven routing. Inside SAP BTP API Management, two runtime patterns are relevant:

External Client


┌──────────────────────────────────────────────┐
│ LAYER 1 — GDCR │
│ Gateway Domain-Centric Routing │
│ │
│ Exposes the immutable semantic facade │
│ Applies authentication and access policies │
│ Loads route metadata from APIM KVM │
│ Passes context into the DDCR resolver │
└──────────────────────┬───────────────────────┘

┌──────────────────────▼───────────────────────┐
│ LAYER 2 — DDCR │
│ Domain Driven-Centric Router │
│ │
│ Parses the semantic URL suffix │
│ Normalizes the action into one code │
│ Resolves KVM metadata │
│ Builds and sets target.url │
└──────────────────────┬───────────────────────┘


SAP CPI / Backend

The script in this post is the DDCR resolver.

It runs as a JavaScript policy in the target request preflow of an SAP API Management proxy. The same JavaScript file can be reused across multiple domain proxies. Each proxy loads a different domain-scoped KVM.

In other words:

The script stays the same. The metadata changes.

That is the core design.

2. What Phantom v12 Actually Does

Phantom v12 receives three required APIM context variables:

Input variable Meaning Example

proxy.pathsuffixThe part of the URL after the APIM proxy base path/orders/create/salesforcekvm.idinterfaceThe domain route metadata loaded from KVMdcrporderscsalesforceid01:http,…target.cpi.hostThe configured backend hosthttps://cpi-host.cfapps.eu10.hana.ondemand.com

It produces one main routing result:

Output variable / header Meaning Example

target.urlFully resolved backend target URLhttps://cpi-host/http/dcrp/orders/c/id01dcrp.routing.successRouting statustrue or falsedcrp.routing.errorError reason when routing failsNo route for: orderscsalesforcex-dcrp-keyMatched KVM keydcrporderscsalesforceid01x-dcrp-adapterAdapter profile from KVM valuehttpx-dcrp-processOptional process prefix extracted from KVM keyempty, finance, o2c, etc.x-idinterfaceRoute ID extracted from key suffix01

If dcrp.routing.success is false, an upstream APIM fault rule can return a controlled error, usually HTTP 404 or 400 depending on the governance rule.

The backend should not be called when the route is not registered.

3. The Contract: What Is Frozen in Phantom v12

Phantom v12 has a specific reference contract.

Frozen in the v12 reference implementation

KVM key format:
dcrp<process><entity><actioncode><vendor>id<digits>

KVM value format:
<adapter>

Path suffix format:
/<entity>/<action>/<vendor>[/<id>[/<extra>…]]

Target URL format:
<host>/<adapter>/dcrp[/<process>]/<entity>/<actionCode>[/idNN][/<id>][/<extra>][?querystring]

Example:

KVM entry:
dcrporderscsalesforceid01:http

Request suffix:
/orders/create/salesforce

Resolved URL:
https://cpi-host/http/dcrp/orders/c/id01

Important: in Phantom v12, the KVM value is not the full target URL suffix.

The value is only the adapter profile:

http
cxf

The engine itself builds the deterministic target path:

/<adapter>/dcrp[/<process>]/<entity>/<actionCode>/id<nn>

So this is correct for v12:

dcrporderscsalesforceid01:http

This is not the v12 format:

dcrporderscsalesforceid01:http/dcrp/orders/c/id01

A company can change the design to store a fuller URL suffix in KVM, but that would require changing the script. It would no longer be Phantom v12 as written.

4. The KVM Metadata Contract

The engine reads route metadata from kvm.idinterface as one comma-separated string.

Example for a Sales domain proxy:

dcrporderscsalesforceid01:http,
dcrpordersusalesforceemeaid02:http,
dcrpcustomerssshopifyid03:http,
dcrppaymentsnstripeid04:http,
dcrporderscmicrosoftid05:cxf,
dcrpdeliveriestfedexid06:http,
dcrpcustomersss4hanaid07:cxf,
dcrppaymentsns4hanaid08:cxf,
dcrpinvoicescquickbooksid09:cxf,
dcrpinvoicescs4hanaid10:cxf,
dcrpdeliveriests4hanaid11:cxf,
dcrpreturnscshopifyid12:http

Each entry follows the same compact structure:

dcrporderscsalesforceid01:http
│ │ │ │ │ └─ adapter profile
│ │ │ │ └────── route id
│ │ │ └──────────────── vendor / sender / target system token
│ │ └────────────────── action code
│ └──────────────────────── entity
└──────────────────────────── fixed prefix

The compact lookup core is:

orderscsalesforce

That core is built from:

entity + actionCode + vendor

So:

orders + c + salesforce = orderscsalesforce

This is why /orders/create/salesforce resolves to the KVM key dcrporderscsalesforceid01.

5. Optional Process Prefix

The v12 key format also allows a process prefix before the entity:

dcrpfinanceinvoicescquickbooksid01:cxf

After removing the prefix dcrp and the suffix id01, the internal stripped key becomes:

financeinvoicescquickbooks

The request provides the entity:

entity = invoices

The engine finds where invoices begins inside the stripped key:

financeinvoicescquickbooks

entity starts here

Everything before invoices is treated as the process prefix:

process = finance

So this request:

/invoices/create/quickbooks

with this KVM entry:

dcrpfinanceinvoicescquickbooksid01:cxf

resolves to:

https://cpi-host/cxf/dcrp/finance/invoices/c/id01

If there is no process prefix:

dcrporderscsalesforceid01:http

then the stripped key is:

orderscsalesforce

The entity orders starts at position 0, so the process is empty.

Resolved URL:

https://cpi-host/http/dcrp/orders/c/id01

No duplicated /orders/orders/ segment is created.

6. Action Normalization

The URL uses readable action words:

/orders/create/salesforce
/orders/update/salesforceemea
/payments/notify/stripe
/deliveries/transfer/fedex
/invoices/approve/basware

The KVM key uses one-character action codes:

Code Meaning Example action words

ccreatecreate, post, insert, add, submit, registerrreadread, get, fetch, retrieve, list, search, queryuupdateupdate, put, patch, modify, change, editddeletedelete, remove, cancel, terminate, destroyssyncsync, synchronize, replicate, mirror, refreshaapproveapprove, authorize, accept, validate, confirmnnotifynotify, alert, inform, announce, broadcastttransfertransfer, send, move, migrate, forwardeenableenable, activate, start, resumebdisabledisable, deactivate, stop, pausevarchivearchive, store, backup, retainwrestorerestore, recover, rollback, revertxauditaudit, log, trace, trackzexecuteexecute, run, process, computefflow / routeflow, route, dispatch, pipeline

The current v12 code contains 241 effective action variants mapped to 15 canonical codes.

The word “effective” matters because JavaScript object keys must be unique. If the same key is declared twice, the later value wins.

In the current Phantom v12 code, deactivate appears in both DELETE and DISABLE sections. The effective value is the last one:

“deactivate”: “b”

So deactivate resolves as disable, not delete.

That should either be accepted as the intended behavior or cleaned in the map to avoid confusion.

7. The Fallback Rule for Unknown Actions

If an action is not found in GLOBAL_ACTION_MAP, Phantom v12 uses a fallback:

var c0 = action.charCodeAt(0);
actionCode = (c0 >= CC_A_LOW && c0 <= CC_Z_LOW) ? action.charAt(0) : ‘x’;

Meaning:

/shipments/query/fedex

works because query is explicitly mapped to r.

But:

/payments/notification/s4hana

only resolves to n if notification is not in the map because the fallback takes the first character:

notification → n

This is useful but permissive.

For strict governance, production teams may prefer to remove the fallback and fail unknown actions explicitly. Phantom v12 keeps the fallback because it is a compact reference implementation and supports flexible action vocabulary.

8. The Semantic URL Contract

In the reference implementation, the APIM proxy base path absorbs tenant and domain context.

Example full URL:

https://<apim>/2271ccfctrial/sales/orders/create/salesforce

APIM proxy base path:

/2271ccfctrial/sales

What Phantom v12 receives as proxy.pathsuffix:

/orders/create/salesforce

The script only parses this suffix:

/<entity>/<action>/<vendor>

So the domain itself is not parsed by the script. The domain is selected before the script runs, by the APIM proxy base path and the KVM loaded for that proxy.

This is important:

One JavaScript policy can be reused across many domain proxies, but each proxy owns its own base path and KVM metadata.

9. Example Route List Used in the Demo

The Newman validation uses 39 tested route calls across four domains.

Finance

/invoices/create/quickbooks
/invoices/create/s4hana
/payments/notify/s4hana
/accounts/sync/xero
/journals/create/sap
/expenses/create/coupa
/budgets/sync/workday
/taxes/create/avalara

If the same path is executed twice to simulate two payloads or two test cases, it should be described as two route executions, not two unique endpoints.

Sales

/orders/create/salesforce
/orders/update/salesforceemea
/customers/sync/shopify
/payments/notify/stripe
/orders/create/microsoft
/deliveries/transfer/fedex
/customers/sync/s4hana
/payments/notification/s4hana
/invoices/create/quickbooks
/invoices/create/s4hana
/deliveries/transfer/s4hana
/returns/create/shopify

Note: notification is not explicitly mapped in the current v12 action map unless added manually. Without explicit mapping, it resolves through the fallback rule to n.

Logistics

/shipments/create/fedex
/trackings/update/ups
/deliveries/create/dhl
/shipments/query/fedex
/containers/sync/maersk
/freights/create/coyote
/routes/sync/project44
/manifests/create/customs
/inventory/sync/wms

Procurement

/requisitions/create/ariba
/pos/create/coupa
/rfqs/create/ariba
/contracts/sync/jaggaer
/invoices/approve/basware
/suppliers/sync/ivalua
/catalogs/update/tradeshift
/grns/create/wms
/buyers/sync/oracle

The exact number depends on whether the validation counts unique suffixes or repeated route executions. The architectural point is not the number itself. The point is that all tested routes use the same resolver logic and no business endpoint is hardcoded in the JavaScript policy.

10. End-to-End Trace

We will follow this request:

POST https://<apim>/2271ccfctrial/sales/orders/create/salesforce

The engine receives:

pathInput = “/orders/create/salesforce”;
kvmInput = “dcrporderscsalesforceid01:http,dcrpordersusalesforceemeaid02:http,…”;
hostInput = “https://cpi-host.cfapps.eu10.hana.ondemand.com”;

Expected result:

target.url = https://cpi-host.cfapps.eu10.hana.ondemand.com/http/dcrp/orders/c/id01

Now we walk through the code.

11. Phase 1 — Fail Fast on Missing Variables

var hostInput = ctx.getVariable(‘target.cpi.host’);
var kvmInput = ctx.getVariable(‘kvm.idinterface’);
var pathInput = ctx.getVariable(‘proxy.pathsuffix’);

if (!hostInput || !kvmInput || !pathInput) {
ctx.setVariable(‘dcrp.routing.success’, ‘false’);
ctx.setVariable(‘dcrp.routing.error’,
‘Missing:’ +
(hostInput ? ” : ‘ [target.cpi.host]’) +
(kvmInput ? ” : ‘ [kvm.idinterface]’) +
(pathInput ? ” : ‘ [proxy.pathsuffix]’)
);
return;
}

The script requires exactly three variables.

If any of them is missing, the script stops immediately and writes a clear error message.

This matters because APIM policies execute in sequence. If the KVM is not loaded before the JavaScript policy runs, the failure should be explicit, not hidden deeper in the routing logic.

For our trace:

hostInput ✓
kvmInput ✓
pathInput ✓

The engine continues.

12. Phase 2 — Host Normalization

if (_cpiHostRaw !== hostInput) {
_cpiHostRaw = hostInput;
var hLen = hostInput.length;
_cpiHostNorm = (hLen > 0 && hostInput.charCodeAt(hLen – 1) === CC_SLASH)
? hostInput.substring(0, hLen – 1)
: hostInput;
}
var host = _cpiHostNorm;

The script removes a trailing slash from the host if one exists.

Example:

https://cpi-host/

becomes:

https://cpi-host

This prevents malformed URLs such as:

https://cpi-host//http/dcrp/orders/c/id01

The normalized host is cached in module-level variables:

var _cpiHostRaw = null;
var _cpiHostNorm = null;

Those variables can survive across requests on the same runtime worker. That reduces repeated normalization work for common steady-state traffic.

For our trace:

hostInput = https://cpi-host.cfapps.eu10.hana.ondemand.com
host = https://cpi-host.cfapps.eu10.hana.ondemand.com

No trailing slash was removed.

13. Phase 3 — Path Parsing Without Regex or Split

Most developers would start with this:

var parts = pathInput.split(‘/’);
var entity = parts[1];
var action = parts[2];
var vendor = parts[3];

Phantom v12 does not use split().

It uses indexOf() and substring():

if (pathInput.charCodeAt(0) !== CC_SLASH) {
ctx.setVariable(‘dcrp.routing.success’, ‘false’);
ctx.setVariable(‘dcrp.routing.error’, ‘Path must start with /’);
return;
}

var s1 = pathInput.indexOf(‘/’, 1);
if (s1 === -1) {
ctx.setVariable(‘dcrp.routing.success’, ‘false’);
ctx.setVariable(‘dcrp.routing.error’, ‘Missing entity segment’);
return;
}

var s2 = pathInput.indexOf(‘/’, s1 + 1);
if (s2 === -1) {
ctx.setVariable(‘dcrp.routing.success’, ‘false’);
ctx.setVariable(‘dcrp.routing.error’, ‘Missing action segment’);
return;
}

var s3 = pathInput.indexOf(‘/’, s2 + 1);

var entity = pathInput.substring(1, s1).toLowerCase();
var action = pathInput.substring(s1 + 1, s2).toLowerCase();
var vendor = (s3 === -1)
? pathInput.substring(s2 + 1)
: pathInput.substring(s2 + 1, s3);
vendor = vendor.toLowerCase();

For this path:

/orders/create/salesforce

positions are:

/orders/create/salesforce
0 7 14
│ │ │
/ / /

So:

entity = orders
action = create
vendor = salesforce

The important correction in v12 is this:

var s3 = pathInput.indexOf(‘/’, s2 + 1);

var vendor = (s3 === -1)
? pathInput.substring(s2 + 1)
: pathInput.substring(s2 + 1, s3);

s3 === -1 is valid.

It means the vendor is the last path segment.

So this is valid:

/orders/create/salesforce

The script should not require a trailing slash after salesforce.

14. Optional ID and Extra Path

Phantom v12 also supports additional path segments after the vendor:

/orders/create/salesforce/12345/items/10

The first segment after vendor becomes id:

id = 12345

Everything after that becomes extraPath:

extraPath = /items/10

The final URL will preserve those segments:

https://cpi-host/http/dcrp/orders/c/id01/12345/items/10

This is useful when the semantic route is stable but the backend iFlow needs an additional business identifier.

However, this also creates a security responsibility:

Extra path segments and query strings are forwarded. If they carry business-sensitive values, the receiving CPI iFlow must validate them.

The DDCR engine validates route registration. It does not validate business payload semantics.

15. Phase 4 — Action Normalization

var actionCode = GLOBAL_ACTION_MAP[action];
if (!actionCode) {
var c0 = action.charCodeAt(0);
actionCode = (c0 >= CC_A_LOW && c0 <= CC_Z_LOW) ? action.charAt(0) : ‘x’;
}

For our trace:

action = create

The map contains:

“create”: “c”

So:

actionCode = c

The request suffix:

/orders/create/salesforce

becomes the lookup pattern:

orderscsalesforce

This allows different systems to use different verbs for the same intent.

Examples:

/create → c
/post → c
/insert → c
/add → c

The route metadata does not need four different entries. It only needs the canonical action code.

16. Phase 5 — KVM Cache

if (_kvmCacheRaw !== kvmInput) {
_kvmCacheRaw = kvmInput;
_kvmCacheMap = parseKvm(kvmInput);
}

The raw KVM string is parsed only when it changes.

On the first request handled by a worker:

_kvmCacheRaw = null

So parseKvm() runs.

On later requests with the same KVM content:

_kvmCacheRaw === kvmInput

So the parsed map is reused.

This is not a distributed cache. It is a worker-scoped runtime cache.

That means:

it can survive across requests on the same worker;

it can be rebuilt when another worker handles traffic;

it is invalidated when the raw KVM string changes;

it should be treated as a performance optimization, not as external state.

17. The parseKvm() Function

The parser receives a comma-separated string:

dcrporderscsalesforceid01:http,dcrpordersusalesforceemeaid02:http

It scans entries using indexOf():

var comma = kvmString.indexOf(‘,’, start);
if (comma === -1) comma = len;

var colon = kvmString.indexOf(‘:’, start);
if (colon === -1 || colon > comma) { start = comma + 1; continue; }

var keyRaw = kvmString.substring(start, colon);
var adapter = kvmString.substring(colon + 1, comma);

For the first entry:

keyRaw = dcrporderscsalesforceid01
adapter = http

Then the parser extracts the numeric ID from the suffix:

id01 → 01

Then it removes:

dcrp from the beginning
id01 from the end

So:

dcrporderscsalesforceid01

becomes:

orderscsalesforce

The parsed map stores:

map[“orderscsalesforce”] = {
adapter: “http”,
id: “01”,
keyOriginal: “dcrporderscsalesforceid01”,
keyLow: “dcrporderscsalesforceid01”,
stripped: “orderscsalesforce”
};

For a process-prefixed key:

dcrpfinanceinvoicescquickbooksid01:cxf

it stores:

map[“financeinvoicescquickbooks”] = {
adapter: “cxf”,
id: “01”,
keyOriginal: “dcrpfinanceinvoicescquickbooksid01”,
stripped: “financeinvoicescquickbooks”
};

The parser does not know the entity at parse time. That is why process extraction happens later during lookup.

18. Phase 6 — Lookup

var lookupPattern = entity + actionCode + vendor;
var match = null;
var matchStripped = null;

var mapKeys = Object.keys(_kvmCacheMap);
for (var mi = 0; mi < mapKeys.length; mi++) {
var mk = mapKeys[mi];
if (mk.indexOf(lookupPattern) !== -1) {
match = _kvmCacheMap[mk];
matchStripped = mk;
break;
}
}

For our trace:

entity = orders
actionCode = c
vendor = salesforce

So:

lookupPattern = orderscsalesforce

The map contains:

orderscsalesforce

So the route matches.

The match result is:

{
adapter: “http”,
id: “01”,
keyOriginal: “dcrporderscsalesforceid01”,
stripped: “orderscsalesforce”
}

If no match is found, the engine stops:

ctx.setVariable(‘dcrp.routing.success’, ‘false’);
ctx.setVariable(‘dcrp.routing.error’, ‘No route for: ‘ + lookupPattern);
return;

Example:

/invoices/create/netsuite

If the KVM does not contain:

dcrpinvoicescnetsuiteidNN:http

then routing fails before the backend is called.

This is the whitelist behavior.

19. Important: v12 Lookup Is O(k), Not True O(1)

Phantom v12 uses:

Object.keys(_kvmCacheMap)

and scans the keys with:

mk.indexOf(lookupPattern) !== -1

So the warm lookup is a small linear scan over the parsed KVM keys.

That is acceptable for small domain KVMs, especially when a domain proxy owns a controlled number of routes.

But it is not a true nested hash lookup.

A later version can optimize this by storing the map as:

map[entity][actionCode][vendor]

Then the lookup becomes direct:

match = map[entity] && map[entity][actionCode] && map[entity][actionCode][vendor];

That would be a performance adaptation.

Phantom v12 intentionally stays simple. It is a reference template, not the final possible optimization.

20. Phase 7 — Process Extraction

var process = ”;
var eIdx = matchStripped.indexOf(entity);
if (eIdx > 0) {
process = matchStripped.substring(0, eIdx);
}

Example without process prefix:

matchStripped = orderscsalesforce
entity = orders
eIdx = 0
process = empty

Example with process prefix:

matchStripped = financeinvoicescquickbooks
entity = invoices
eIdx = 7
process = finance

This allows the same engine to support both compact keys:

dcrporderscsalesforceid01:http

and process-prefixed keys:

dcrpfinanceinvoicescquickbooksid01:cxf

But there is a limitation:

Because Phantom v12 uses indexOf(entity), the process prefix should not contain the entity token in a way that creates ambiguity.

For example, if the process contains a substring that looks like the entity, extraction can become ambiguous.

This is why Phantom v12 should be treated as a governed template. The naming convention must be respected.

21. Phase 8 — Building the Target URL

var url = host + ‘/’ + match.adapter + ‘/dcrp’;
if (process) url += ‘/’ + process;
url += ‘/’ + entity + ‘/’ + actionCode;

if (match.id) url += ‘/id’ + match.id;
if (id) url += ‘/’ + id;
if (extraPath) url += extraPath;

var qs = ctx.getVariable(‘request.querystring’);
if (qs) url += ‘?’ + qs;

For our trace:

host = https://cpi-host.cfapps.eu10.hana.ondemand.com
adapter = http
process = empty
entity = orders
actionCode = c
match.id = 01
id = empty
extraPath = empty
querystring = empty

Step by step:

1. https://cpi-host.cfapps.eu10.hana.ondemand.com/http/dcrp
2. process is empty, so skip it
3. https://cpi-host.cfapps.eu10.hana.ondemand.com/http/dcrp/orders/c
4. https://cpi-host.cfapps.eu10.hana.ondemand.com/http/dcrp/orders/c/id01

Final result:

https://cpi-host.cfapps.eu10.hana.ondemand.com/http/dcrp/orders/c/id01

This is the CPI sender endpoint path exposed by the target iFlow.

It is not a ProcessDirect address from APIM to CPI.

APIM calls CPI through an externally reachable HTTP or SOAP/CXF sender endpoint. ProcessDirect may be used inside CPI between iFlows, but that is a CPI-internal design choice and not what this APIM target URL represents.

22. Phase 9 — Writing Output Variables and Headers

var packageName = ctx.getVariable(‘kvm.packagename’) || ‘unknown’;
var sapProcess = ctx.getVariable(‘kvm.sapprocess’) || ‘unknown’;
var messageId = ctx.getVariable(‘messageid’) || ”;

ctx.setVariable(‘dcrp.routing.success’, ‘true’);
ctx.setVariable(‘target.url’, url);
ctx.setVariable(‘request.header.x-dcrp-process’, process);
ctx.setVariable(‘request.header.x-dcrp-adapter’, match.adapter);
ctx.setVariable(‘request.header.x-dcrp-version’, ‘20.0’);
ctx.setVariable(‘request.header.x-dcrp-key’, match.keyOriginal);
ctx.setVariable(‘request.header.x-packagename’, packageName);
ctx.setVariable(‘request.header.x-sapprocess’, sapProcess);
ctx.setVariable(‘request.header.x-senderid’, vendor);
ctx.setVariable(‘request.header.x-correlationid’, messageId);
ctx.setVariable(‘request.header.x-idinterface’, match.id);

On success, the engine writes:

dcrp.routing.success = true
target.url = resolved backend URL

It also writes traceability headers.

For our trace:

x-dcrp-key = dcrporderscsalesforceid01
x-dcrp-adapter = http
x-dcrp-process = empty
x-senderid = salesforce
x-idinterface = 01

If the key had a process prefix:

dcrpfinanceinvoicescquickbooksid01:cxf

then:

x-dcrp-process = finance

These headers help APIM and CPI monitoring correlate the request with the KVM entry and target iFlow.

23. Complete Trace Summary

Input:

POST https://<apim>/2271ccfctrial/sales/orders/create/salesforce

proxy.pathsuffix = /orders/create/salesforce
kvm.idinterface = dcrporderscsalesforceid01:http,…
target.cpi.host = https://cpi-host.cfapps.eu10.hana.ondemand.com

Execution:

Phase 1 — variables present
Phase 2 — host normalized
Phase 3 — path parsed
entity = orders
action = create
vendor = salesforce
Phase 4 — action normalized
create → c
Phase 5 — KVM cache checked
Phase 5b — parseKvm builds map on cache miss
Phase 6 — lookupPattern = orderscsalesforce
match found
Phase 7 — process extraction
process = empty
Phase 8 — target URL built
Phase 9 — headers written

Output:

target.url = https://cpi-host.cfapps.eu10.hana.ondemand.com/http/dcrp/orders/c/id01
x-dcrp-key = dcrporderscsalesforceid01
x-idinterface = 01
dcrp.routing.success = true

Result:

APIM routes the request to the CPI sender endpoint assigned to route id01.

24. How a New Route Is Added

Scenario:

A new system called Netsuite must receive invoice create requests in the Finance domain.

The client wants to call:

/invoices/create/netsuite

The engine will build:

entity + actionCode + vendor

So:

invoices + c + netsuite = invoicescnetsuite

Before adding the route, the KVM does not contain it.

Result:

dcrp.routing.success = false
dcrp.routing.error = No route for: invoicescnetsuite

To activate the route, add one KVM entry:

dcrpinvoicescnetsuiteid11:http

Next request:

/invoices/create/netsuite

matches:

invoicescnetsuite

and resolves to:

https://cpi-host/http/dcrp/invoices/c/id11

No proxy redeployment is required if the APIM KVM update is immediately available to the runtime.

The proxy does not change.

The KVM grows.

25. What Can Be Adapted

Phantom v12 is a reference template. Companies can adapt it.

Safe adaptations without changing the core idea

Proxy base path depth:
/{tenant}/{domain}
/{company}/{region}/{domain}
/{company}/{region}/{domain}/{subdomain}

Domain KVM ownership:
one KVM per domain
one KVM per subdomain
one KVM per region/domain pair

Route ID length:
id01
id001
id0001

Output headers:
add company-specific monitoring headers
rename headers according to internal standards

Adapter vocabulary:
http
cxf
rest
soap
odata

But these changes may require code changes depending on where the adaptation happens.

For example, changing route ID length usually works because the parser extracts all digits after id.

Changing KVM adapter names also works if the final CPI URL uses those adapter path segments.

Changing the entire target URL format requires code changes.

26. What Is Not Automatically Adaptable

These changes are not automatically supported by Phantom v12 as written:

1. Dot-separated KVM keys

This is supported:

dcrporderscsalesforceid01:http

This is not supported without parser changes:

dcrp.sales.o2c.orders.c.salesforce.id01:http

Dot-separated names are fine for packages, proxies, and iFlows. But Phantom v12 KVM keys are compact.

2. Different suffix grammar

This is supported:

/<entity>/<action>/<vendor>

This requires code adaptation:

/<domain>/<subdomain>/<entity>/<action>/<vendor>
/<entity>/<vendor>/<action>
/<action>/<entity>/<vendor>

The parser expects entity first, action second, vendor third.

3. Full URL stored as KVM value

This is not v12 behavior:

dcrporderscsalesforceid01:http/dcrp/orders/c/id01

The v12 KVM value is only:

http

If a company wants the full target suffix in KVM, the URL builder must be changed.

4. Direct APIM-to-SAP OData URL routing

Phantom v12 works best when APIM routes to a deterministic CPI endpoint.

It should not be used as an OData URL constructor for direct APIM-to-S/4HANA point-to-point calls.

OData routes may include entity sets, key predicates, navigation paths, $metadata, $filter, $expand, and method-specific behavior. That is a different routing problem.

For direct APIM-to-SAP backend routing, standard APIM policies or a specialized OData-aware policy design are more appropriate.

27. CPI Endpoint Contract

When SAP CPI is the backend, the resolved target URL points to an HTTP or SOAP/CXF sender endpoint exposed by a CPI iFlow.

Example:

https://cpi-host/http/dcrp/orders/c/id01

This path should correspond to an iFlow sender adapter address.

The iFlow name can follow ODCP naming governance, for example:

id01.o2c.salesforce.order.sap.c.in.sync

But the engine does not call the iFlow name.

The engine calls the URL.

The relationship is governance-based:

KVM key:
dcrporderscsalesforceid01:http

Target URL:
/http/dcrp/orders/c/id01

CPI iFlow name:
id01.o2c.salesforce.order.sap.c.in.sync

The shared ID id01 links the metadata, endpoint, and iFlow name.

That is the traceability contract.

28. ODCP iFlow Naming Reference

A practical ODCP-style iFlow naming format is:

id{nn}.{subdomain}.{vendor}.{resource}.{system}.{operation}.{direction}.{mode}

Example:

id01.o2c.salesforce.order.sap.c.in.sync

Meaning:

Segment Meaning Example

id01Route indexid01o2cBusiness subdomainorder-to-cashsalesforceVendor/source/target tokenSalesforceorderBusiness resourceordersapPlatform/system referenceSAPcOperation codecreateinDirectioninboundsyncProcessing modesynchronous

The index is important because it connects three things:

KVM key → dcrporderscsalesforceid01
Target URL → /http/dcrp/orders/c/id01
CPI iFlow → id01.o2c.salesforce.order.sap.c.in.sync

When support teams see:

x-idinterface: 01

they immediately know which KVM entry and which CPI iFlow are involved.

29. Security Boundary

Phantom v12 does not route to arbitrary client-provided hosts.

The client controls the semantic suffix:

/<entity>/<action>/<vendor>

But the backend host comes from APIM configuration:

target.cpi.host

The adapter and route ID come from KVM:

dcrporderscsalesforceid01:http

So the KVM acts as a whitelist.

If the route is not registered, routing fails.

This prevents a client from inventing a new backend target by changing the URL.

However, Phantom v12 does forward:

optional ID segment
extra path segments
query string

Therefore:

DDCR validates route registration. CPI must validate business-level parameters.

The engine is a router, not a full business authorization layer.

30. Known Limitations of Phantom v12

Phantom v12 is intentionally compact. These are its important boundaries.

1. Lookup is linear over map keys

Warm requests avoid re-parsing the KVM, but lookup still scans Object.keys(_kvmCacheMap).

For small domain KVMs this is acceptable.

For very large KVMs, a nested hash map is better.

2. indexOf() can create ambiguous matches

The lookup uses:

mk.indexOf(lookupPattern) !== -1

That allows process-prefixed keys, but it also means naming must be governed.

If one key accidentally contains another route pattern as a substring, the first match wins.

A stricter future version can store keys by exact entity/action/vendor structure.

3. The KVM prefix is assumed, not strongly validated

The parser does:

keyLow.substring(4)

That assumes the key begins with dcrp.

A hardened version should explicitly validate:

key starts with dcrp
key contains id<digits>
key has non-empty adapter

4. Duplicate semantic routes overwrite each other

Because the parsed map is a JavaScript object, if two entries produce the same stripped key, the later one wins.

Example:

dcrporderscsalesforceid01:http
dcrporderscsalesforceid99:cxf

Both produce:

orderscsalesforce

Only one survives in the map.

5. Unknown action fallback is permissive

If an action is not in the map, the first letter is used.

This is flexible, but stricter companies may want unknown actions to fail.

6. It is not a universal URL router

If the company changes the URL grammar, the script must be adapted.

Phantom v12 is a reference engine, not a universal parser for every possible enterprise URL.

31. Performance Profile — Correctly Framed

Phantom v12 is optimized for simplicity and low allocation pressure:

No regex
No split
Worker-scoped KVM cache
Worker-scoped host normalization cache
Manual path parsing
Compact action codes

The code comments describe this profile:

Cold start: ~3–4ms when parseKvm runs
Warm request: ~1.5–2.5ms when cache is reused
KVM size: up to ~200 entries without expected degradation in this demo profile
Memory: <5KB per worker for small maps

These numbers should be treated as environment-dependent measurements, not universal guarantees.

Runtime behavior depends on:

APIM tenant
Rhino engine behavior
policy chain
KVM size
worker reuse
network path to backend
measurement method

Also, Phantom v12 is not the final possible performance design.

If performance becomes the priority, the natural adaptations are:

1. Replace linear key scan with nested hash lookup.
2. Add strict exact matching.
3. Add a one-entry micro-cache for repeated paths.
4. Precompute more route metadata during parseKvm.
5. Remove permissive fallback if governance requires strictness.

That is why Phantom v12 should be understood as both:

a working reference implementation
and
a base template for company-specific adaptation

32. What Happens When a Company Needs a Different URL

Suppose the company wants this URL:

/{tenant}/{region}/{domain}/{subdomain}/{entity}/{action}/{vendor}

There are two options.

Option A — Put region/domain/subdomain in the APIM base path

Proxy base path:

/{tenant}/{region}/{domain}/{subdomain}

The script still receives:

/<entity>/<action>/<vendor>

No script change is required.

Option B — Let the script parse region/domain/subdomain

If proxy.pathsuffix becomes:

/{subdomain}/{entity}/{action}/{vendor}

then Phantom v12 must be changed.

The current parser expects:

/<entity>/<action>/<vendor>

So this is the governance rule:

If extra URL dimensions are placed before the suffix, APIM base path can absorb them. If extra dimensions are placed inside the suffix, the script must be adapted.

This is why Phantom v12 is a template, not a universal router.

33. When DDCR Should Be Used

DDCR is a good fit when the backend target is deterministic.

Best case:

External client

APIM GDCR/DDCR proxy

CPI iFlow sender endpoint

CPI handles orchestration, mapping, protocol conversion

SAP / non-SAP backend

Here, APIM only needs to resolve the semantic route to a CPI endpoint.

CPI handles the backend complexity.

This is where Phantom v12 fits well.

34. When DDCR Should Not Be Used

DDCR should not be forced into direct APIM-to-SAP point-to-point scenarios where the backend URL is not deterministic.

Examples:

POST /CustomerOrderCollection
GET /CustomerOrderCollection(‘ObjectID’)
PATCH /CustomerOrderPriceComponentCollection(‘ObjectID’)
GET /salesorder/$metadata

Those are OData-specific patterns.

They depend on entity sets, key predicates, navigation properties, query options, and HTTP method semantics.

Trying to rebuild that inside a generic DDCR JavaScript policy would be the wrong abstraction.

Decision rule:

Is CPI the deterministic target behind APIM?
YES → DDCR fits.
NO → use APIM policies or a specialized backend-specific routing design.

35. Why This Matters

Traditional endpoint growth often creates:

one proxy per endpoint
one deployment per new route
duplicated routing logic
hardcoded target paths
unclear ownership
weak traceability

Phantom v12 demonstrates a different model:

one reusable JavaScript policy
one domain-scoped KVM
one semantic suffix grammar
one deterministic URL builder
one traceable idNN contract

The JavaScript policy does not know that Salesforce exists as a business route.

It does not know that QuickBooks exists as a business route.

It does not know that FedEx exists as a business route.

It only knows how to parse:

entity/action/vendor

normalize:

action → actionCode

match:

entity + actionCode + vendor

and build:

target.url

The KVM owns the route catalog.

That is the architectural separation.

36. The Main Principle

The main principle is simple:

The proxy does not change for every new business route. The metadata changes.

More precisely:

The JavaScript policy is stable.
The APIM proxy facade is stable.
The route catalog grows in KVM.
The CPI iFlow endpoint contract follows the route ID.

This is the practical meaning of metadata-driven routing in Phantom v12.

It does not mean the script will fit every company without adaptation.

It means the script provides a clean reference base from which adaptation can happen without losing the architectural principle.

37. Download and Run

The reference artifacts include:

APIM proxy artifacts
KVM templates
Phantom v12 JavaScript policy
Newman collection
CPI iFlow stubs

Repository:

https://github.com/rhviana/deip/tree/main/gdcr-proven/gdcr-drcp-sap-api

Example Newman command:

newman run GDCR-DDCR-Phantom-v12.json -n 2

Example parallel execution on Windows:

start “S1” cmd /k newman run GDCR-DDCR-Phantom-v12.json -n 10
start “S2” cmd /k newman run GDCR-DDCR-Phantom-v12.json -n 10
start “S3” cmd /k newman run GDCR-DDCR-Phantom-v12.json -n 10
start “S4” cmd /k newman run GDCR-DDCR-Phantom-v12.json -n 10

When reporting test results, separate:

unique routes
route executions
total HTTP calls
parallel sessions
iterations per session

That avoids confusion when the same route is executed more than once with different payloads or test cases.

38. References

SDIA v3.0
DOI: 10.5281/zenodo.18877635

ODCP v3.0
DOI: 10.5281/zenodo.18876593

GDCR + DDCR v3.0
DOI: 10.5281/zenodo.18864833

GDCR Routing Engine — Phantom v12
DOI: 10.5281/zenodo.18619641

GitHub
https://github.com/rhviana/gdcr

License:

CC BY 4.0

Author:

Ricardo Luz Holanda Viana
Independent Solo Researcher
Enterprise Integration Architect
SAP BTP Integration Suite Expert
SAP Press e-Bite Author — Enterprise Messaging, 2021
Creator of DEIP, SDIA, GDCR, DDCR, ODCP, EDCP, DDCP
ORCID: 0009-0009-9549-5862
www.domain-intent.com

Final Statement

Phantom v12 is not trying to be the most abstract router possible.

It is doing something more useful:

Parse one semantic URL suffix.
Normalize one action.
Resolve one KVM route.
Build one deterministic target URL.
Expose one traceable integration contract.

That is enough to replace endpoint sprawl with governed metadata.

It is a reference template, not a universal plugin.

It can be adapted.
It can be optimized.
It can be hardened.

But the architectural principle remains stable:

The proxy never changes for the route. The KVM grows.

GDCR exposes the semantic facade.
DDCR resolves the runtime route.
ODCP governs the CPI layer behind it.

Same domain intent.
Same metadata discipline.
Same architecture.

That is SDIA in execution.

Apendix A –  Phantom v12 – JavaScript Policy the DDCR – Domain Driven-Centric Router.

/**
* ============================================================================
* GDCR Routing Engine – Phantom v12 (PRODUCTION READY)
* ============================================================================
* Author: Ricardo Luz Holanda Viana
* Email: rhviana@gmail.com
* License: CC BY 4.0 (https://creativecommons.org/licenses/by/4.0/)
* DOI: https://doi.org/10.5281/zenodo.18619641
* GitHub: https://github.com/rhviana/deip
*
* Zero regex | Zero hardcode | Zero split | O(k) lookup after warm cache
* Compatible: SAP BTP APIM (Rhino) | Apigee | Kong (with adapter)
* ============================================================================
*
* KVM key format: dcrp<process><entity><actioncode><vendor>id<digits>
* KVM value format: <adapter>
* Example:
* Key: dcrpfinanceorderscsalesforceid01
* Value: http
*
* Path format: /<entity>/<action>/<vendor>[/<id>[/<extra>…]]
* ============================================================================
*/

// ── Canonical Action Map — Phantom v12 ──────────────────────────────────────
// 247 action variants → 15 canonical single-character codes
// Author: Ricardo Luz Holanda Viana
// DOI: 10.5281/zenodo.18582492

var GLOBAL_ACTION_MAP = {

// ── c: CREATE (27 variants) ──────────────────────────────────────────────
“create”:”c”, “created”:”c”, “post”:”c”,
“insert”:”c”, “add”:”c”, “new”:”c”,
“submit”:”c”, “register”:”c”, “registered”:”c”,
“provision”:”c”, “provisioning”:”c”,”onboard”:”c”,
“onboarding”:”c”,”publish”:”c”, “publishing”:”c”,
“generate”:”c”, “build”:”c”, “built”:”c”,
“open”:”c”, “opening”:”c”, “setup”:”c”,
“initialize”:”c”,”initializing”:”c”,”initialized”:”c”,
“draft”:”c”,

// ── r: READ (25 variants) ────────────────────────────────────────────────
“read”:”r”, “get”:”r”, “fetch”:”r”,
“retrieve”:”r”, “view”:”r”, “viewed”:”r”,
“list”:”r”, “listing”:”r”, “listed”:”r”,
“show”:”r”, “showing”:”r”, “shown”:”r”,
“search”:”r”, “searching”:”r”, “searched”:”r”,
“find”:”r”, “finding”:”r”, “found”:”r”,
“query”:”r”, “querying”:”r”, “queried”:”r”,
“lookup”:”r”, “check”:”r”, “inspect”:”r”,

// ── u: UPDATE (27 variants) ──────────────────────────────────────────────
“update”:”u”, “updating”:”u”, “updated”:”u”,
“put”:”u”, “patch”:”u”, “patching”:”u”,
“modify”:”u”, “modifying”:”u”, “modified”:”u”,
“change”:”u”, “changing”:”u”, “changed”:”u”,
“edit”:”u”, “editing”:”u”, “edited”:”u”,
“revise”:”u”, “revising”:”u”, “revised”:”u”,
“amend”:”u”, “amending”:”u”, “amended”:”u”,
“replace”:”u”, “replacing”:”u”, “replaced”:”u”,
“upsert”:”u”,

// ── d: DELETE (20 variants) ──────────────────────────────────────────────
“delete”:”d”, “deleting”:”d”, “deleted”:”d”,
“remove”:”d”, “removing”:”d”, “removed”:”d”,
“cancel”:”d”, “cancelling”:”d”, “cancelled”:”d”,
“terminate”:”d”, “terminating”:”d”,”terminated”:”d”,
“destroy”:”d”, “destroying”:”d”, “destroyed”:”d”,
“deactivate”:”d”,”purge”:”d”, “purging”:”d”,
“purged”:”d”, “discard”:”d”,

// ── s: SYNC (18 variants) ────────────────────────────────────────────────
“sync”:”s”, “syncing”:”s”, “synced”:”s”,
“synchronize”:”s”, “synchronizing”:”s”, “synchronized”:”s”,
“replicate”:”s”, “replicating”:”s”, “replicated”:”s”,
“mirror”:”s”, “mirroring”:”s”, “mirrored”:”s”,
“refresh”:”s”, “refreshing”:”s”, “refreshed”:”s”,
“reconcile”:”s”, “harmonize”:”s”, “align”:”s”,

// ── a: APPROVE (18 variants) ─────────────────────────────────────────────
“approve”:”a”, “approving”:”a”, “approved”:”a”,
“authorize”:”a”, “authorizing”:”a”, “authorized”:”a”,
“accept”:”a”, “accepting”:”a”, “accepted”:”a”,
“validate”:”a”, “validating”:”a”, “validated”:”a”,
“confirm”:”a”, “confirming”:”a”, “confirmed”:”a”,
“endorse”:”a”, “certify”:”a”, “signoff”:”a”,

// ── n: NOTIFY (18 variants) ──────────────────────────────────────────────
“notify”:”n”, “notifying”:”n”, “notified”:”n”,
“alert”:”n”, “alerting”:”n”, “alerted”:”n”,
“inform”:”n”, “informing”:”n”, “informed”:”n”,
“announce”:”n”, “announcing”:”n”, “announced”:”n”,
“broadcast”:”n”, “broadcasting”:”n”,”broadcasted”:”n”,
“emit”:”n”, “trigger”:”n”, “signal”:”n”,

// ── t: TRANSFER (16 variants) ────────────────────────────────────────────
“transfer”:”t”, “transferring”:”t”,”transferred”:”t”,
“send”:”t”, “sending”:”t”, “sent”:”t”,
“move”:”t”, “moving”:”t”, “moved”:”t”,
“migrate”:”t”, “migrating”:”t”, “migrated”:”t”,
“forward”:”t”, “forwarding”:”t”, “relay”:”t”,
“handoff”:”t”,

// ── e: ENABLE (12 variants) ──────────────────────────────────────────────
“enable”:”e”, “enabling”:”e”, “enabled”:”e”,
“activate”:”e”, “activating”:”e”, “activated”:”e”,
“start”:”e”, “starting”:”e”, “started”:”e”,
“resume”:”e”, “resuming”:”e”, “resumed”:”e”,

// ── b: DISABLE (12 variants) ─────────────────────────────────────────────
“disable”:”b”, “disabling”:”b”, “disabled”:”b”,
“deactivate”:”b”, “deactivating”:”b”,”deactivated”:”b”,
“stop”:”b”, “stopping”:”b”, “stopped”:”b”,
“pause”:”b”, “pausing”:”b”, “paused”:”b”,

// ── v: ARCHIVE (10 variants) ─────────────────────────────────────────────
“archive”:”v”, “archiving”:”v”, “archived”:”v”,
“store”:”v”, “storing”:”v”, “stored”:”v”,
“backup”:”v”, “backing”:”v”, “backed”:”v”,
“retain”:”v”,

// ── w: RESTORE (10 variants) ─────────────────────────────────────────────
“restore”:”w”, “restoring”:”w”, “restored”:”w”,
“unarchive”:”w”, “recover”:”w”, “recovering”:”w”,
“recovered”:”w”, “rollback”:”w”, “revert”:”w”,
“reinstate”:”w”,

// ── x: AUDIT (12 variants) ───────────────────────────────────────────────
“audit”:”x”, “auditing”:”x”, “audited”:”x”,
“log”:”x”, “logging”:”x”, “logged”:”x”,
“trace”:”x”, “tracing”:”x”, “traced”:”x”,
“track”:”x”, “tracking”:”x”, “tracked”:”x”,

// ── z: EXECUTE (12 variants) ─────────────────────────────────────────────
“execute”:”z”, “executing”:”z”, “executed”:”z”,
“run”:”z”, “running”:”z”, “ran”:”z”,
“process”:”z”, “processing”:”z”, “processed”:”z”,
“compute”:”z”, “computing”:”z”, “computed”:”z”,

// ── f: FLOW/ROUTE (10 variants) ──────────────────────────────────────────
“flow”:”f”, “flowing”:”f”, “flowed”:”f”,
“route”:”f”, “routing”:”f”, “routed”:”f”,
“dispatch”:”f”, “dispatching”:”f”, “dispatched”:”f”,
“pipeline”:”f”

};

// Validation
var entries = Object.keys(GLOBAL_ACTION_MAP);
var codes = {};
entries.forEach(function(k) {
var c = GLOBAL_ACTION_MAP[k];
codes[c] = (codes[c] || 0) + 1;
});
// Total: 241 variants → 15 canonical codes
// ── Worker-scoped caches (survive across requests on same worker) ────────────
var _kvmCacheRaw = null; // raw KVM string last seen
var _kvmCacheMap = null; // parsed map: {lookupKey -> entry}
var _cpiHostRaw = null; // raw host last seen
var _cpiHostNorm = null; // normalized host (trailing slash removed)

// ── Char code constants (zero string allocation on comparison) ───────────────
var CC_SLASH = 47; // ‘/’
var CC_UNDER = 95; // ‘_’
var CC_0 = 48; // ‘0’
var CC_9 = 57; // ‘9’
var CC_A_LOW = 97; // ‘a’
var CC_Z_LOW = 122; // ‘z’

// ============================================================================
// parseKvm — runs ONLY when KVM string changes (amortized O(n) per request)
//
// Strategy for process extraction (zero hardcode, zero regex):
// Key structure: dcrp + <process> + <entity> + <actioncode> + <vendor> + id<digits>
// We know: entity (from request path) — BUT at parse time we don’t have entity.
// Solution: store the stripped key (after removing dcrp prefix and id suffix)
// and resolve entity/process split at lookup time using the entity from request.
// This keeps parse O(n) over KVM entries and lookup O(k) over map keys (k<=30).
// ============================================================================
function parseKvm(kvmString) {
var map = {};
var len = kvmString.length;
var start = 0;

while (start < len) {

// ── Find entry boundaries ──────────────────────────────────────────────
var comma = kvmString.indexOf(‘,’, start);
if (comma === -1) comma = len;

var colon = kvmString.indexOf(‘:’, start);
if (colon === -1 || colon > comma) { start = comma + 1; continue; }

var keyRaw = kvmString.substring(start, colon);
var adapter = kvmString.substring(colon + 1, comma);

if (!keyRaw || !adapter) { start = comma + 1; continue; }

var keyLow = keyRaw.toLowerCase();

// ── Extract numeric ID from end of key (e.g. “id01” → “01”) ──────────
var idVal = ”;
var idPos = keyLow.lastIndexOf(‘id’);
if (idPos !== -1 && idPos < keyLow.length – 2) {
var dStr = ”;
for (var di = idPos + 2; di < keyLow.length; di++) {
var dc = keyLow.charCodeAt(di);
if (dc >= CC_0 && dc <= CC_9) dStr += keyLow.charAt(di);
else break;
}
if (dStr) idVal = dStr;
}

// ── Strip ‘dcrp’ prefix and ‘id<digits>’ suffix ───────────────────────
// Result: <process><entity><actioncode><vendor>
var stripped = keyLow.substring(4); // remove leading ‘dcrp’
if (idVal) {
// remove trailing ‘id’ + digits
stripped = stripped.substring(0, stripped.length – (2 + idVal.length));
}

// ── Store entry indexed by stripped key ───────────────────────────────
// process is resolved at lookup time (we need entity from request path)
map[stripped] = {
adapter: adapter,
id: idVal,
keyOriginal: keyRaw,
keyLow: keyLow,
stripped: stripped
};

start = comma + 1;
}
return map;
}

// ============================================================================
// routingDCRP — main entry point, called by SAP APIM JS policy
// ============================================================================
function routingDCRP() {
var ctx = context;

// ── Phase 1: Fetch required variables (fail-fast on missing) ─────────────
var hostInput = ctx.getVariable(‘target.cpi.host’);
var kvmInput = ctx.getVariable(‘kvm.idinterface’);
var pathInput = ctx.getVariable(‘proxy.pathsuffix’);

if (!hostInput || !kvmInput || !pathInput) {
ctx.setVariable(‘dcrp.routing.success’, ‘false’);
ctx.setVariable(‘dcrp.routing.error’,
‘Missing:’ +
(hostInput ? ” : ‘ [target.cpi.host]’) +
(kvmInput ? ” : ‘ [kvm.idinterface]’) +
(pathInput ? ” : ‘ [proxy.pathsuffix]’)
);
return;
}

// ── Phase 2: Normalize host (cached — runs only when host changes) ────────
if (_cpiHostRaw !== hostInput) {
_cpiHostRaw = hostInput;
var hLen = hostInput.length;
_cpiHostNorm = (hLen > 0 && hostInput.charCodeAt(hLen – 1) === CC_SLASH)
? hostInput.substring(0, hLen – 1)
: hostInput;
}
var host = _cpiHostNorm;

// ── Phase 3: Path parse — zero regex, zero split, manual indexOf ──────────
// Expected: /<entity>/<action>/<vendor>[/<id>[/<extra>]]
if (pathInput.charCodeAt(0) !== CC_SLASH) {
ctx.setVariable(‘dcrp.routing.success’, ‘false’);
ctx.setVariable(‘dcrp.routing.error’, ‘Path must start with /’);
return;
}

var s1 = pathInput.indexOf(‘/’, 1);
if (s1 === -1) {
ctx.setVariable(‘dcrp.routing.success’, ‘false’);
ctx.setVariable(‘dcrp.routing.error’, ‘Missing entity segment’);
return;
}

var s2 = pathInput.indexOf(‘/’, s1 + 1);
if (s2 === -1) {
ctx.setVariable(‘dcrp.routing.success’, ‘false’);
ctx.setVariable(‘dcrp.routing.error’, ‘Missing action segment’);
return;
}

// ── FIX: s3 === -1 is valid — vendor is the last path segment ────────────
var s3 = pathInput.indexOf(‘/’, s2 + 1);

var entity = pathInput.substring(1, s1).toLowerCase();
var action = pathInput.substring(s1 + 1, s2).toLowerCase();
var vendor = (s3 === -1)
? pathInput.substring(s2 + 1)
: pathInput.substring(s2 + 1, s3);
vendor = vendor.toLowerCase();

// ── FIX: strip optional leading underscore — was vendor.substring(n) ─────
if (vendor.length > 0 && vendor.charCodeAt(0) === CC_UNDER) {
vendor = vendor.substring(1);
}

// Optional: id and extra path segments (only meaningful when s3 exists)
var id = ”;
var extraPath = ”;
if (s3 !== -1) {
var s4 = pathInput.indexOf(‘/’, s3 + 1);
id = (s4 === -1) ? pathInput.substring(s3 + 1) : pathInput.substring(s3 + 1, s4);
extraPath = (s4 === -1) ? ” : pathInput.substring(s4);
}

if (!entity || !action || !vendor) {
ctx.setVariable(‘dcrp.routing.success’, ‘false’);
ctx.setVariable(‘dcrp.routing.error’, ‘Empty path segment’);
return;
}

// ── Phase 4: Action normalization — O(n) hash lookup, char fallback ───────
var actionCode = GLOBAL_ACTION_MAP[action];
if (!actionCode) {
var c0 = action.charCodeAt(0);
actionCode = (c0 >= CC_A_LOW && c0 <= CC_Z_LOW) ? action.charAt(0) : ‘x’;
}

// ── Phase 5: KVM cache — rebuild only when raw string changes ─────────────
if (_kvmCacheRaw !== kvmInput) {
_kvmCacheRaw = kvmInput;
_kvmCacheMap = parseKvm(kvmInput);
}

// ── Phase 6: Lookup — O(k) over map, k <= 30, ~0.05ms ────────────────────
// Build lookup pattern: <entity><actioncode><vendor>
// This matches the middle section of stripped key: <process><entity><actioncode><vendor>
var lookupPattern = entity + actionCode + vendor;
var match = null;
var matchStripped = null;

var mapKeys = Object.keys(_kvmCacheMap);
for (var mi = 0; mi < mapKeys.length; mi++) {
var mk = mapKeys[mi];
if (mk.indexOf(lookupPattern) !== -1) {
match = _kvmCacheMap[mk];
matchStripped = mk;
break;
}
}

if (!match) {
ctx.setVariable(‘dcrp.routing.success’, ‘false’);
ctx.setVariable(‘dcrp.routing.error’, ‘No route for: ‘ + lookupPattern);
return;
}

// ── Phase 7: Process extraction — fully dynamic, zero hardcode ───────────
// stripped = <process><entity><actioncode><vendor>
// We know entity from request path → find its position → process = everything before it
// When eIdx === 0 there is no process prefix — domain-only key, process is empty
var process = ”;
var eIdx = matchStripped.indexOf(entity);
if (eIdx > 0) {
process = matchStripped.substring(0, eIdx);
}

// ── Phase 8: Build target URL ─────────────────────────────────────────────
// Skip process segment when empty to avoid duplication in URL
var url = host + ‘/’ + match.adapter + ‘/dcrp’;
if (process) url += ‘/’ + process;
url += ‘/’ + entity + ‘/’ + actionCode;

if (match.id) url += ‘/id’ + match.id;
if (id) url += ‘/’ + id;
if (extraPath) url += extraPath;

var qs = ctx.getVariable(‘request.querystring’);
if (qs) url += ‘?’ + qs;

// ── Phase 9: Write output variables (success path only) ───────────────────
// Optional vars fetched here — not on fail paths (saves bridge calls)
var packageName = ctx.getVariable(‘kvm.packagename’) || ‘unknown’;
var sapProcess = ctx.getVariable(‘kvm.sapprocess’) || ‘unknown’;
var messageId = ctx.getVariable(‘messageid’) || ”;

ctx.setVariable(‘dcrp.routing.success’, ‘true’);
ctx.setVariable(‘target.url’, url);
ctx.setVariable(‘request.header.x-dcrp-process’, process);
ctx.setVariable(‘request.header.x-dcrp-adapter’, match.adapter);
ctx.setVariable(‘request.header.x-dcrp-version’, ‘12.0’);
ctx.setVariable(‘request.header.x-dcrp-key’, match.keyOriginal);
ctx.setVariable(‘request.header.x-packagename’, packageName);
ctx.setVariable(‘request.header.x-sapprocess’, sapProcess);
ctx.setVariable(‘request.header.x-senderid’, vendor);
ctx.setVariable(‘request.header.x-correlationid’, messageId);
ctx.setVariable(‘request.header.x-idinterface’, match.id);
}

// ============================================================================
// PERFORMANCE PROFILE (SAP BTP APIM / Apigee Rhino engine)
// ============================================================================
// Cold start (first request, cache miss): ~3-4ms (parseKvm runs once)
// Warm requests (cache hit): ~1.5-2.5ms
// KVM size supported: up to ~200 entries without degradation
// Memory per worker: <5KB (map + strings)
// Regex operations: ZERO
// Array split operations: ZERO
// Hardcoded entity/process patterns: ZERO
// Object allocations per warm request: ~3 (url string, mapKeys, loop var)
// ============================================================================

 

 

​ How a Single JavaScript Policy Routes 39 Tested SAP Integration Paths Across 4 Domains Without Hardcoded Business RoutesZero regex. Zero split. Zero hardcoded business routes. The KVM defines the route.This post explains the GDCR Routing Engine — Phantom v12, a JavaScript policy used inside SAP BTP API Management to resolve semantic inbound URLs into deterministic SAP CPI target URLs.This is not a universal plug-and-play router for every enterprise URL structure. It is a reference template: a compact, production-oriented base that can be adapted by companies according to their own URL depth, KVM structure, naming convention, routing granularity, and performance requirements.The important distinction is this:Phantom v12 is stable as a reference implementation, but it is not 100% automatically adaptable to every company landscape without adjustment.If a company changes the URL grammar, KVM key format, route metadata model, adapter rules, or output headers, the script must be adapted. That is normal. The value of Phantom v12 is not that it magically fits every architecture. The value is that it gives a clear, deterministic, metadata-driven routing template that avoids hardcoded business endpoints inside the proxy. 1. Where This Script Lives in SDIABefore opening the code, it is important to understand where this JavaScript policy sits.SDIA — Semantic Domain Integration Architecture organizes the integration landscape around domain-owned artifacts and metadata-driven routing. Inside SAP BTP API Management, two runtime patterns are relevant:External Client


┌──────────────────────────────────────────────┐
│ LAYER 1 — GDCR │
│ Gateway Domain-Centric Routing │
│ │
│ Exposes the immutable semantic facade │
│ Applies authentication and access policies │
│ Loads route metadata from APIM KVM │
│ Passes context into the DDCR resolver │
└──────────────────────┬───────────────────────┘

┌──────────────────────▼───────────────────────┐
│ LAYER 2 — DDCR │
│ Domain Driven-Centric Router │
│ │
│ Parses the semantic URL suffix │
│ Normalizes the action into one code │
│ Resolves KVM metadata │
│ Builds and sets target.url │
└──────────────────────┬───────────────────────┘


SAP CPI / BackendThe script in this post is the DDCR resolver.It runs as a JavaScript policy in the target request preflow of an SAP API Management proxy. The same JavaScript file can be reused across multiple domain proxies. Each proxy loads a different domain-scoped KVM.In other words:The script stays the same. The metadata changes.That is the core design.2. What Phantom v12 Actually DoesPhantom v12 receives three required APIM context variables:Input variable Meaning Exampleproxy.pathsuffixThe part of the URL after the APIM proxy base path/orders/create/salesforcekvm.idinterfaceThe domain route metadata loaded from KVMdcrporderscsalesforceid01:http,…target.cpi.hostThe configured backend hosthttps://cpi-host.cfapps.eu10.hana.ondemand.comIt produces one main routing result:Output variable / header Meaning Exampletarget.urlFully resolved backend target URLhttps://cpi-host/http/dcrp/orders/c/id01dcrp.routing.successRouting statustrue or falsedcrp.routing.errorError reason when routing failsNo route for: orderscsalesforcex-dcrp-keyMatched KVM keydcrporderscsalesforceid01x-dcrp-adapterAdapter profile from KVM valuehttpx-dcrp-processOptional process prefix extracted from KVM keyempty, finance, o2c, etc.x-idinterfaceRoute ID extracted from key suffix01If dcrp.routing.success is false, an upstream APIM fault rule can return a controlled error, usually HTTP 404 or 400 depending on the governance rule.The backend should not be called when the route is not registered.3. The Contract: What Is Frozen in Phantom v12Phantom v12 has a specific reference contract.Frozen in the v12 reference implementationKVM key format:
dcrp<process><entity><actioncode><vendor>id<digits>

KVM value format:
<adapter>

Path suffix format:
/<entity>/<action>/<vendor>[/<id>[/<extra>…]]

Target URL format:
<host>/<adapter>/dcrp[/<process>]/<entity>/<actionCode>[/idNN][/<id>][/<extra>][?querystring]Example:KVM entry:
dcrporderscsalesforceid01:http

Request suffix:
/orders/create/salesforce

Resolved URL:
https://cpi-host/http/dcrp/orders/c/id01Important: in Phantom v12, the KVM value is not the full target URL suffix.The value is only the adapter profile:http
cxfThe engine itself builds the deterministic target path:/<adapter>/dcrp[/<process>]/<entity>/<actionCode>/id<nn>So this is correct for v12:dcrporderscsalesforceid01:httpThis is not the v12 format:dcrporderscsalesforceid01:http/dcrp/orders/c/id01A company can change the design to store a fuller URL suffix in KVM, but that would require changing the script. It would no longer be Phantom v12 as written.4. The KVM Metadata ContractThe engine reads route metadata from kvm.idinterface as one comma-separated string.Example for a Sales domain proxy:dcrporderscsalesforceid01:http,
dcrpordersusalesforceemeaid02:http,
dcrpcustomerssshopifyid03:http,
dcrppaymentsnstripeid04:http,
dcrporderscmicrosoftid05:cxf,
dcrpdeliveriestfedexid06:http,
dcrpcustomersss4hanaid07:cxf,
dcrppaymentsns4hanaid08:cxf,
dcrpinvoicescquickbooksid09:cxf,
dcrpinvoicescs4hanaid10:cxf,
dcrpdeliveriests4hanaid11:cxf,
dcrpreturnscshopifyid12:httpEach entry follows the same compact structure:dcrporderscsalesforceid01:http
│ │ │ │ │ └─ adapter profile
│ │ │ │ └────── route id
│ │ │ └──────────────── vendor / sender / target system token
│ │ └────────────────── action code
│ └──────────────────────── entity
└──────────────────────────── fixed prefixThe compact lookup core is:orderscsalesforceThat core is built from:entity + actionCode + vendorSo:orders + c + salesforce = orderscsalesforceThis is why /orders/create/salesforce resolves to the KVM key dcrporderscsalesforceid01.5. Optional Process PrefixThe v12 key format also allows a process prefix before the entity:dcrpfinanceinvoicescquickbooksid01:cxfAfter removing the prefix dcrp and the suffix id01, the internal stripped key becomes:financeinvoicescquickbooksThe request provides the entity:entity = invoicesThe engine finds where invoices begins inside the stripped key:financeinvoicescquickbooks

entity starts hereEverything before invoices is treated as the process prefix:process = financeSo this request:/invoices/create/quickbookswith this KVM entry:dcrpfinanceinvoicescquickbooksid01:cxfresolves to:https://cpi-host/cxf/dcrp/finance/invoices/c/id01If there is no process prefix:dcrporderscsalesforceid01:httpthen the stripped key is:orderscsalesforceThe entity orders starts at position 0, so the process is empty.Resolved URL:https://cpi-host/http/dcrp/orders/c/id01No duplicated /orders/orders/ segment is created.6. Action NormalizationThe URL uses readable action words:/orders/create/salesforce
/orders/update/salesforceemea
/payments/notify/stripe
/deliveries/transfer/fedex
/invoices/approve/baswareThe KVM key uses one-character action codes:Code Meaning Example action wordsccreatecreate, post, insert, add, submit, registerrreadread, get, fetch, retrieve, list, search, queryuupdateupdate, put, patch, modify, change, editddeletedelete, remove, cancel, terminate, destroyssyncsync, synchronize, replicate, mirror, refreshaapproveapprove, authorize, accept, validate, confirmnnotifynotify, alert, inform, announce, broadcastttransfertransfer, send, move, migrate, forwardeenableenable, activate, start, resumebdisabledisable, deactivate, stop, pausevarchivearchive, store, backup, retainwrestorerestore, recover, rollback, revertxauditaudit, log, trace, trackzexecuteexecute, run, process, computefflow / routeflow, route, dispatch, pipelineThe current v12 code contains 241 effective action variants mapped to 15 canonical codes.The word “effective” matters because JavaScript object keys must be unique. If the same key is declared twice, the later value wins.In the current Phantom v12 code, deactivate appears in both DELETE and DISABLE sections. The effective value is the last one:”deactivate”: “b”So deactivate resolves as disable, not delete.That should either be accepted as the intended behavior or cleaned in the map to avoid confusion.7. The Fallback Rule for Unknown ActionsIf an action is not found in GLOBAL_ACTION_MAP, Phantom v12 uses a fallback:var c0 = action.charCodeAt(0);
actionCode = (c0 >= CC_A_LOW && c0 <= CC_Z_LOW) ? action.charAt(0) : ‘x’;Meaning:/shipments/query/fedexworks because query is explicitly mapped to r.But:/payments/notification/s4hanaonly resolves to n if notification is not in the map because the fallback takes the first character:notification → nThis is useful but permissive.For strict governance, production teams may prefer to remove the fallback and fail unknown actions explicitly. Phantom v12 keeps the fallback because it is a compact reference implementation and supports flexible action vocabulary.8. The Semantic URL ContractIn the reference implementation, the APIM proxy base path absorbs tenant and domain context.Example full URL:https://<apim>/2271ccfctrial/sales/orders/create/salesforceAPIM proxy base path:/2271ccfctrial/salesWhat Phantom v12 receives as proxy.pathsuffix:/orders/create/salesforceThe script only parses this suffix:/<entity>/<action>/<vendor>So the domain itself is not parsed by the script. The domain is selected before the script runs, by the APIM proxy base path and the KVM loaded for that proxy.This is important:One JavaScript policy can be reused across many domain proxies, but each proxy owns its own base path and KVM metadata.9. Example Route List Used in the DemoThe Newman validation uses 39 tested route calls across four domains.Finance/invoices/create/quickbooks
/invoices/create/s4hana
/payments/notify/s4hana
/accounts/sync/xero
/journals/create/sap
/expenses/create/coupa
/budgets/sync/workday
/taxes/create/avalaraIf the same path is executed twice to simulate two payloads or two test cases, it should be described as two route executions, not two unique endpoints.Sales/orders/create/salesforce
/orders/update/salesforceemea
/customers/sync/shopify
/payments/notify/stripe
/orders/create/microsoft
/deliveries/transfer/fedex
/customers/sync/s4hana
/payments/notification/s4hana
/invoices/create/quickbooks
/invoices/create/s4hana
/deliveries/transfer/s4hana
/returns/create/shopifyNote: notification is not explicitly mapped in the current v12 action map unless added manually. Without explicit mapping, it resolves through the fallback rule to n.Logistics/shipments/create/fedex
/trackings/update/ups
/deliveries/create/dhl
/shipments/query/fedex
/containers/sync/maersk
/freights/create/coyote
/routes/sync/project44
/manifests/create/customs
/inventory/sync/wmsProcurement/requisitions/create/ariba
/pos/create/coupa
/rfqs/create/ariba
/contracts/sync/jaggaer
/invoices/approve/basware
/suppliers/sync/ivalua
/catalogs/update/tradeshift
/grns/create/wms
/buyers/sync/oracleThe exact number depends on whether the validation counts unique suffixes or repeated route executions. The architectural point is not the number itself. The point is that all tested routes use the same resolver logic and no business endpoint is hardcoded in the JavaScript policy.10. End-to-End TraceWe will follow this request:POST https://<apim>/2271ccfctrial/sales/orders/create/salesforceThe engine receives:pathInput = “/orders/create/salesforce”;
kvmInput = “dcrporderscsalesforceid01:http,dcrpordersusalesforceemeaid02:http,…”;
hostInput = “https://cpi-host.cfapps.eu10.hana.ondemand.com”;Expected result:target.url = https://cpi-host.cfapps.eu10.hana.ondemand.com/http/dcrp/orders/c/id01Now we walk through the code.11. Phase 1 — Fail Fast on Missing Variablesvar hostInput = ctx.getVariable(‘target.cpi.host’);
var kvmInput = ctx.getVariable(‘kvm.idinterface’);
var pathInput = ctx.getVariable(‘proxy.pathsuffix’);

if (!hostInput || !kvmInput || !pathInput) {
ctx.setVariable(‘dcrp.routing.success’, ‘false’);
ctx.setVariable(‘dcrp.routing.error’,
‘Missing:’ +
(hostInput ? ” : ‘ [target.cpi.host]’) +
(kvmInput ? ” : ‘ [kvm.idinterface]’) +
(pathInput ? ” : ‘ [proxy.pathsuffix]’)
);
return;
}The script requires exactly three variables.If any of them is missing, the script stops immediately and writes a clear error message.This matters because APIM policies execute in sequence. If the KVM is not loaded before the JavaScript policy runs, the failure should be explicit, not hidden deeper in the routing logic.For our trace:hostInput ✓
kvmInput ✓
pathInput ✓The engine continues.12. Phase 2 — Host Normalizationif (_cpiHostRaw !== hostInput) {
_cpiHostRaw = hostInput;
var hLen = hostInput.length;
_cpiHostNorm = (hLen > 0 && hostInput.charCodeAt(hLen – 1) === CC_SLASH)
? hostInput.substring(0, hLen – 1)
: hostInput;
}
var host = _cpiHostNorm;The script removes a trailing slash from the host if one exists.Example:https://cpi-host/becomes:https://cpi-hostThis prevents malformed URLs such as:https://cpi-host//http/dcrp/orders/c/id01The normalized host is cached in module-level variables:var _cpiHostRaw = null;
var _cpiHostNorm = null;Those variables can survive across requests on the same runtime worker. That reduces repeated normalization work for common steady-state traffic.For our trace:hostInput = https://cpi-host.cfapps.eu10.hana.ondemand.com
host = https://cpi-host.cfapps.eu10.hana.ondemand.comNo trailing slash was removed.13. Phase 3 — Path Parsing Without Regex or SplitMost developers would start with this:var parts = pathInput.split(‘/’);
var entity = parts[1];
var action = parts[2];
var vendor = parts[3];Phantom v12 does not use split().It uses indexOf() and substring():if (pathInput.charCodeAt(0) !== CC_SLASH) {
ctx.setVariable(‘dcrp.routing.success’, ‘false’);
ctx.setVariable(‘dcrp.routing.error’, ‘Path must start with /’);
return;
}

var s1 = pathInput.indexOf(‘/’, 1);
if (s1 === -1) {
ctx.setVariable(‘dcrp.routing.success’, ‘false’);
ctx.setVariable(‘dcrp.routing.error’, ‘Missing entity segment’);
return;
}

var s2 = pathInput.indexOf(‘/’, s1 + 1);
if (s2 === -1) {
ctx.setVariable(‘dcrp.routing.success’, ‘false’);
ctx.setVariable(‘dcrp.routing.error’, ‘Missing action segment’);
return;
}

var s3 = pathInput.indexOf(‘/’, s2 + 1);

var entity = pathInput.substring(1, s1).toLowerCase();
var action = pathInput.substring(s1 + 1, s2).toLowerCase();
var vendor = (s3 === -1)
? pathInput.substring(s2 + 1)
: pathInput.substring(s2 + 1, s3);
vendor = vendor.toLowerCase();For this path:/orders/create/salesforcepositions are:/orders/create/salesforce
0 7 14
│ │ │
/ / /So:entity = orders
action = create
vendor = salesforceThe important correction in v12 is this:var s3 = pathInput.indexOf(‘/’, s2 + 1);

var vendor = (s3 === -1)
? pathInput.substring(s2 + 1)
: pathInput.substring(s2 + 1, s3);s3 === -1 is valid.It means the vendor is the last path segment.So this is valid:/orders/create/salesforceThe script should not require a trailing slash after salesforce.14. Optional ID and Extra PathPhantom v12 also supports additional path segments after the vendor:/orders/create/salesforce/12345/items/10The first segment after vendor becomes id:id = 12345Everything after that becomes extraPath:extraPath = /items/10The final URL will preserve those segments:https://cpi-host/http/dcrp/orders/c/id01/12345/items/10This is useful when the semantic route is stable but the backend iFlow needs an additional business identifier.However, this also creates a security responsibility:Extra path segments and query strings are forwarded. If they carry business-sensitive values, the receiving CPI iFlow must validate them.The DDCR engine validates route registration. It does not validate business payload semantics.15. Phase 4 — Action Normalizationvar actionCode = GLOBAL_ACTION_MAP[action];
if (!actionCode) {
var c0 = action.charCodeAt(0);
actionCode = (c0 >= CC_A_LOW && c0 <= CC_Z_LOW) ? action.charAt(0) : ‘x’;
}For our trace:action = createThe map contains:”create”: “c”So:actionCode = cThe request suffix:/orders/create/salesforcebecomes the lookup pattern:orderscsalesforceThis allows different systems to use different verbs for the same intent.Examples:/create → c
/post → c
/insert → c
/add → cThe route metadata does not need four different entries. It only needs the canonical action code.16. Phase 5 — KVM Cacheif (_kvmCacheRaw !== kvmInput) {
_kvmCacheRaw = kvmInput;
_kvmCacheMap = parseKvm(kvmInput);
}The raw KVM string is parsed only when it changes.On the first request handled by a worker:_kvmCacheRaw = nullSo parseKvm() runs.On later requests with the same KVM content:_kvmCacheRaw === kvmInputSo the parsed map is reused.This is not a distributed cache. It is a worker-scoped runtime cache.That means:it can survive across requests on the same worker;it can be rebuilt when another worker handles traffic;it is invalidated when the raw KVM string changes;it should be treated as a performance optimization, not as external state.17. The parseKvm() FunctionThe parser receives a comma-separated string:dcrporderscsalesforceid01:http,dcrpordersusalesforceemeaid02:httpIt scans entries using indexOf():var comma = kvmString.indexOf(‘,’, start);
if (comma === -1) comma = len;

var colon = kvmString.indexOf(‘:’, start);
if (colon === -1 || colon > comma) { start = comma + 1; continue; }

var keyRaw = kvmString.substring(start, colon);
var adapter = kvmString.substring(colon + 1, comma);For the first entry:keyRaw = dcrporderscsalesforceid01
adapter = httpThen the parser extracts the numeric ID from the suffix:id01 → 01Then it removes:dcrp from the beginning
id01 from the endSo:dcrporderscsalesforceid01becomes:orderscsalesforceThe parsed map stores:map[“orderscsalesforce”] = {
adapter: “http”,
id: “01”,
keyOriginal: “dcrporderscsalesforceid01”,
keyLow: “dcrporderscsalesforceid01”,
stripped: “orderscsalesforce”
};For a process-prefixed key:dcrpfinanceinvoicescquickbooksid01:cxfit stores:map[“financeinvoicescquickbooks”] = {
adapter: “cxf”,
id: “01”,
keyOriginal: “dcrpfinanceinvoicescquickbooksid01”,
stripped: “financeinvoicescquickbooks”
};The parser does not know the entity at parse time. That is why process extraction happens later during lookup.18. Phase 6 — Lookupvar lookupPattern = entity + actionCode + vendor;
var match = null;
var matchStripped = null;

var mapKeys = Object.keys(_kvmCacheMap);
for (var mi = 0; mi < mapKeys.length; mi++) {
var mk = mapKeys[mi];
if (mk.indexOf(lookupPattern) !== -1) {
match = _kvmCacheMap[mk];
matchStripped = mk;
break;
}
}For our trace:entity = orders
actionCode = c
vendor = salesforceSo:lookupPattern = orderscsalesforceThe map contains:orderscsalesforceSo the route matches.The match result is:{
adapter: “http”,
id: “01”,
keyOriginal: “dcrporderscsalesforceid01”,
stripped: “orderscsalesforce”
}If no match is found, the engine stops:ctx.setVariable(‘dcrp.routing.success’, ‘false’);
ctx.setVariable(‘dcrp.routing.error’, ‘No route for: ‘ + lookupPattern);
return;Example:/invoices/create/netsuiteIf the KVM does not contain:dcrpinvoicescnetsuiteidNN:httpthen routing fails before the backend is called.This is the whitelist behavior.19. Important: v12 Lookup Is O(k), Not True O(1)Phantom v12 uses:Object.keys(_kvmCacheMap)and scans the keys with:mk.indexOf(lookupPattern) !== -1So the warm lookup is a small linear scan over the parsed KVM keys.That is acceptable for small domain KVMs, especially when a domain proxy owns a controlled number of routes.But it is not a true nested hash lookup.A later version can optimize this by storing the map as:map[entity][actionCode][vendor]Then the lookup becomes direct:match = map[entity] && map[entity][actionCode] && map[entity][actionCode][vendor];That would be a performance adaptation.Phantom v12 intentionally stays simple. It is a reference template, not the final possible optimization.20. Phase 7 — Process Extractionvar process = ”;
var eIdx = matchStripped.indexOf(entity);
if (eIdx > 0) {
process = matchStripped.substring(0, eIdx);
}Example without process prefix:matchStripped = orderscsalesforce
entity = orders
eIdx = 0
process = emptyExample with process prefix:matchStripped = financeinvoicescquickbooks
entity = invoices
eIdx = 7
process = financeThis allows the same engine to support both compact keys:dcrporderscsalesforceid01:httpand process-prefixed keys:dcrpfinanceinvoicescquickbooksid01:cxfBut there is a limitation:Because Phantom v12 uses indexOf(entity), the process prefix should not contain the entity token in a way that creates ambiguity.For example, if the process contains a substring that looks like the entity, extraction can become ambiguous.This is why Phantom v12 should be treated as a governed template. The naming convention must be respected.21. Phase 8 — Building the Target URLvar url = host + ‘/’ + match.adapter + ‘/dcrp’;
if (process) url += ‘/’ + process;
url += ‘/’ + entity + ‘/’ + actionCode;

if (match.id) url += ‘/id’ + match.id;
if (id) url += ‘/’ + id;
if (extraPath) url += extraPath;

var qs = ctx.getVariable(‘request.querystring’);
if (qs) url += ‘?’ + qs;For our trace:host = https://cpi-host.cfapps.eu10.hana.ondemand.com
adapter = http
process = empty
entity = orders
actionCode = c
match.id = 01
id = empty
extraPath = empty
querystring = emptyStep by step:1. https://cpi-host.cfapps.eu10.hana.ondemand.com/http/dcrp
2. process is empty, so skip it
3. https://cpi-host.cfapps.eu10.hana.ondemand.com/http/dcrp/orders/c
4. https://cpi-host.cfapps.eu10.hana.ondemand.com/http/dcrp/orders/c/id01Final result:https://cpi-host.cfapps.eu10.hana.ondemand.com/http/dcrp/orders/c/id01This is the CPI sender endpoint path exposed by the target iFlow.It is not a ProcessDirect address from APIM to CPI.APIM calls CPI through an externally reachable HTTP or SOAP/CXF sender endpoint. ProcessDirect may be used inside CPI between iFlows, but that is a CPI-internal design choice and not what this APIM target URL represents.22. Phase 9 — Writing Output Variables and Headersvar packageName = ctx.getVariable(‘kvm.packagename’) || ‘unknown’;
var sapProcess = ctx.getVariable(‘kvm.sapprocess’) || ‘unknown’;
var messageId = ctx.getVariable(‘messageid’) || ”;

ctx.setVariable(‘dcrp.routing.success’, ‘true’);
ctx.setVariable(‘target.url’, url);
ctx.setVariable(‘request.header.x-dcrp-process’, process);
ctx.setVariable(‘request.header.x-dcrp-adapter’, match.adapter);
ctx.setVariable(‘request.header.x-dcrp-version’, ‘20.0’);
ctx.setVariable(‘request.header.x-dcrp-key’, match.keyOriginal);
ctx.setVariable(‘request.header.x-packagename’, packageName);
ctx.setVariable(‘request.header.x-sapprocess’, sapProcess);
ctx.setVariable(‘request.header.x-senderid’, vendor);
ctx.setVariable(‘request.header.x-correlationid’, messageId);
ctx.setVariable(‘request.header.x-idinterface’, match.id);On success, the engine writes:dcrp.routing.success = true
target.url = resolved backend URLIt also writes traceability headers.For our trace:x-dcrp-key = dcrporderscsalesforceid01
x-dcrp-adapter = http
x-dcrp-process = empty
x-senderid = salesforce
x-idinterface = 01If the key had a process prefix:dcrpfinanceinvoicescquickbooksid01:cxfthen:x-dcrp-process = financeThese headers help APIM and CPI monitoring correlate the request with the KVM entry and target iFlow.23. Complete Trace SummaryInput:POST https://<apim>/2271ccfctrial/sales/orders/create/salesforce

proxy.pathsuffix = /orders/create/salesforce
kvm.idinterface = dcrporderscsalesforceid01:http,…
target.cpi.host = https://cpi-host.cfapps.eu10.hana.ondemand.comExecution:Phase 1 — variables present
Phase 2 — host normalized
Phase 3 — path parsed
entity = orders
action = create
vendor = salesforce
Phase 4 — action normalized
create → c
Phase 5 — KVM cache checked
Phase 5b — parseKvm builds map on cache miss
Phase 6 — lookupPattern = orderscsalesforce
match found
Phase 7 — process extraction
process = empty
Phase 8 — target URL built
Phase 9 — headers writtenOutput:target.url = https://cpi-host.cfapps.eu10.hana.ondemand.com/http/dcrp/orders/c/id01
x-dcrp-key = dcrporderscsalesforceid01
x-idinterface = 01
dcrp.routing.success = trueResult:APIM routes the request to the CPI sender endpoint assigned to route id01.24. How a New Route Is AddedScenario:A new system called Netsuite must receive invoice create requests in the Finance domain.The client wants to call:/invoices/create/netsuiteThe engine will build:entity + actionCode + vendorSo:invoices + c + netsuite = invoicescnetsuiteBefore adding the route, the KVM does not contain it.Result:dcrp.routing.success = false
dcrp.routing.error = No route for: invoicescnetsuiteTo activate the route, add one KVM entry:dcrpinvoicescnetsuiteid11:httpNext request:/invoices/create/netsuitematches:invoicescnetsuiteand resolves to:https://cpi-host/http/dcrp/invoices/c/id11No proxy redeployment is required if the APIM KVM update is immediately available to the runtime.The proxy does not change.The KVM grows.25. What Can Be AdaptedPhantom v12 is a reference template. Companies can adapt it.Safe adaptations without changing the core ideaProxy base path depth:
/{tenant}/{domain}
/{company}/{region}/{domain}
/{company}/{region}/{domain}/{subdomain}

Domain KVM ownership:
one KVM per domain
one KVM per subdomain
one KVM per region/domain pair

Route ID length:
id01
id001
id0001

Output headers:
add company-specific monitoring headers
rename headers according to internal standards

Adapter vocabulary:
http
cxf
rest
soap
odataBut these changes may require code changes depending on where the adaptation happens.For example, changing route ID length usually works because the parser extracts all digits after id.Changing KVM adapter names also works if the final CPI URL uses those adapter path segments.Changing the entire target URL format requires code changes.26. What Is Not Automatically AdaptableThese changes are not automatically supported by Phantom v12 as written:1. Dot-separated KVM keysThis is supported:dcrporderscsalesforceid01:httpThis is not supported without parser changes:dcrp.sales.o2c.orders.c.salesforce.id01:httpDot-separated names are fine for packages, proxies, and iFlows. But Phantom v12 KVM keys are compact.2. Different suffix grammarThis is supported:/<entity>/<action>/<vendor>This requires code adaptation:/<domain>/<subdomain>/<entity>/<action>/<vendor>
/<entity>/<vendor>/<action>
/<action>/<entity>/<vendor>The parser expects entity first, action second, vendor third.3. Full URL stored as KVM valueThis is not v12 behavior:dcrporderscsalesforceid01:http/dcrp/orders/c/id01The v12 KVM value is only:httpIf a company wants the full target suffix in KVM, the URL builder must be changed.4. Direct APIM-to-SAP OData URL routingPhantom v12 works best when APIM routes to a deterministic CPI endpoint.It should not be used as an OData URL constructor for direct APIM-to-S/4HANA point-to-point calls.OData routes may include entity sets, key predicates, navigation paths, $metadata, $filter, $expand, and method-specific behavior. That is a different routing problem.For direct APIM-to-SAP backend routing, standard APIM policies or a specialized OData-aware policy design are more appropriate.27. CPI Endpoint ContractWhen SAP CPI is the backend, the resolved target URL points to an HTTP or SOAP/CXF sender endpoint exposed by a CPI iFlow.Example:https://cpi-host/http/dcrp/orders/c/id01This path should correspond to an iFlow sender adapter address.The iFlow name can follow ODCP naming governance, for example:id01.o2c.salesforce.order.sap.c.in.syncBut the engine does not call the iFlow name.The engine calls the URL.The relationship is governance-based:KVM key:
dcrporderscsalesforceid01:http

Target URL:
/http/dcrp/orders/c/id01

CPI iFlow name:
id01.o2c.salesforce.order.sap.c.in.syncThe shared ID id01 links the metadata, endpoint, and iFlow name.That is the traceability contract.28. ODCP iFlow Naming ReferenceA practical ODCP-style iFlow naming format is:id{nn}.{subdomain}.{vendor}.{resource}.{system}.{operation}.{direction}.{mode}Example:id01.o2c.salesforce.order.sap.c.in.syncMeaning:Segment Meaning Exampleid01Route indexid01o2cBusiness subdomainorder-to-cashsalesforceVendor/source/target tokenSalesforceorderBusiness resourceordersapPlatform/system referenceSAPcOperation codecreateinDirectioninboundsyncProcessing modesynchronousThe index is important because it connects three things:KVM key → dcrporderscsalesforceid01
Target URL → /http/dcrp/orders/c/id01
CPI iFlow → id01.o2c.salesforce.order.sap.c.in.syncWhen support teams see:x-idinterface: 01they immediately know which KVM entry and which CPI iFlow are involved.29. Security BoundaryPhantom v12 does not route to arbitrary client-provided hosts.The client controls the semantic suffix:/<entity>/<action>/<vendor>But the backend host comes from APIM configuration:target.cpi.hostThe adapter and route ID come from KVM:dcrporderscsalesforceid01:httpSo the KVM acts as a whitelist.If the route is not registered, routing fails.This prevents a client from inventing a new backend target by changing the URL.However, Phantom v12 does forward:optional ID segment
extra path segments
query stringTherefore:DDCR validates route registration. CPI must validate business-level parameters.The engine is a router, not a full business authorization layer.30. Known Limitations of Phantom v12Phantom v12 is intentionally compact. These are its important boundaries.1. Lookup is linear over map keysWarm requests avoid re-parsing the KVM, but lookup still scans Object.keys(_kvmCacheMap).For small domain KVMs this is acceptable.For very large KVMs, a nested hash map is better.2. indexOf() can create ambiguous matchesThe lookup uses:mk.indexOf(lookupPattern) !== -1That allows process-prefixed keys, but it also means naming must be governed.If one key accidentally contains another route pattern as a substring, the first match wins.A stricter future version can store keys by exact entity/action/vendor structure.3. The KVM prefix is assumed, not strongly validatedThe parser does:keyLow.substring(4)That assumes the key begins with dcrp.A hardened version should explicitly validate:key starts with dcrp
key contains id<digits>
key has non-empty adapter4. Duplicate semantic routes overwrite each otherBecause the parsed map is a JavaScript object, if two entries produce the same stripped key, the later one wins.Example:dcrporderscsalesforceid01:http
dcrporderscsalesforceid99:cxfBoth produce:orderscsalesforceOnly one survives in the map.5. Unknown action fallback is permissiveIf an action is not in the map, the first letter is used.This is flexible, but stricter companies may want unknown actions to fail.6. It is not a universal URL routerIf the company changes the URL grammar, the script must be adapted.Phantom v12 is a reference engine, not a universal parser for every possible enterprise URL.31. Performance Profile — Correctly FramedPhantom v12 is optimized for simplicity and low allocation pressure:No regex
No split
Worker-scoped KVM cache
Worker-scoped host normalization cache
Manual path parsing
Compact action codesThe code comments describe this profile:Cold start: ~3–4ms when parseKvm runs
Warm request: ~1.5–2.5ms when cache is reused
KVM size: up to ~200 entries without expected degradation in this demo profile
Memory: <5KB per worker for small mapsThese numbers should be treated as environment-dependent measurements, not universal guarantees.Runtime behavior depends on:APIM tenant
Rhino engine behavior
policy chain
KVM size
worker reuse
network path to backend
measurement methodAlso, Phantom v12 is not the final possible performance design.If performance becomes the priority, the natural adaptations are:1. Replace linear key scan with nested hash lookup.
2. Add strict exact matching.
3. Add a one-entry micro-cache for repeated paths.
4. Precompute more route metadata during parseKvm.
5. Remove permissive fallback if governance requires strictness.That is why Phantom v12 should be understood as both:a working reference implementation
and
a base template for company-specific adaptation32. What Happens When a Company Needs a Different URLSuppose the company wants this URL:/{tenant}/{region}/{domain}/{subdomain}/{entity}/{action}/{vendor}There are two options.Option A — Put region/domain/subdomain in the APIM base pathProxy base path:/{tenant}/{region}/{domain}/{subdomain}The script still receives:/<entity>/<action>/<vendor>No script change is required.Option B — Let the script parse region/domain/subdomainIf proxy.pathsuffix becomes:/{subdomain}/{entity}/{action}/{vendor}then Phantom v12 must be changed.The current parser expects:/<entity>/<action>/<vendor>So this is the governance rule:If extra URL dimensions are placed before the suffix, APIM base path can absorb them. If extra dimensions are placed inside the suffix, the script must be adapted.This is why Phantom v12 is a template, not a universal router.33. When DDCR Should Be UsedDDCR is a good fit when the backend target is deterministic.Best case:External client

APIM GDCR/DDCR proxy

CPI iFlow sender endpoint

CPI handles orchestration, mapping, protocol conversion

SAP / non-SAP backendHere, APIM only needs to resolve the semantic route to a CPI endpoint.CPI handles the backend complexity.This is where Phantom v12 fits well.34. When DDCR Should Not Be UsedDDCR should not be forced into direct APIM-to-SAP point-to-point scenarios where the backend URL is not deterministic.Examples:POST /CustomerOrderCollection
GET /CustomerOrderCollection(‘ObjectID’)
PATCH /CustomerOrderPriceComponentCollection(‘ObjectID’)
GET /salesorder/$metadataThose are OData-specific patterns.They depend on entity sets, key predicates, navigation properties, query options, and HTTP method semantics.Trying to rebuild that inside a generic DDCR JavaScript policy would be the wrong abstraction.Decision rule:Is CPI the deterministic target behind APIM?
YES → DDCR fits.
NO → use APIM policies or a specialized backend-specific routing design.35. Why This MattersTraditional endpoint growth often creates:one proxy per endpoint
one deployment per new route
duplicated routing logic
hardcoded target paths
unclear ownership
weak traceabilityPhantom v12 demonstrates a different model:one reusable JavaScript policy
one domain-scoped KVM
one semantic suffix grammar
one deterministic URL builder
one traceable idNN contractThe JavaScript policy does not know that Salesforce exists as a business route.It does not know that QuickBooks exists as a business route.It does not know that FedEx exists as a business route.It only knows how to parse:entity/action/vendornormalize:action → actionCodematch:entity + actionCode + vendorand build:target.urlThe KVM owns the route catalog.That is the architectural separation.36. The Main PrincipleThe main principle is simple:The proxy does not change for every new business route. The metadata changes.More precisely:The JavaScript policy is stable.
The APIM proxy facade is stable.
The route catalog grows in KVM.
The CPI iFlow endpoint contract follows the route ID.This is the practical meaning of metadata-driven routing in Phantom v12.It does not mean the script will fit every company without adaptation.It means the script provides a clean reference base from which adaptation can happen without losing the architectural principle.37. Download and RunThe reference artifacts include:APIM proxy artifacts
KVM templates
Phantom v12 JavaScript policy
Newman collection
CPI iFlow stubsRepository:https://github.com/rhviana/deip/tree/main/gdcr-proven/gdcr-drcp-sap-apiExample Newman command:newman run GDCR-DDCR-Phantom-v12.json -n 2Example parallel execution on Windows:start “S1” cmd /k newman run GDCR-DDCR-Phantom-v12.json -n 10
start “S2” cmd /k newman run GDCR-DDCR-Phantom-v12.json -n 10
start “S3” cmd /k newman run GDCR-DDCR-Phantom-v12.json -n 10
start “S4” cmd /k newman run GDCR-DDCR-Phantom-v12.json -n 10When reporting test results, separate:unique routes
route executions
total HTTP calls
parallel sessions
iterations per sessionThat avoids confusion when the same route is executed more than once with different payloads or test cases.38. ReferencesSDIA v3.0
DOI: 10.5281/zenodo.18877635

ODCP v3.0
DOI: 10.5281/zenodo.18876593

GDCR + DDCR v3.0
DOI: 10.5281/zenodo.18864833

GDCR Routing Engine — Phantom v12
DOI: 10.5281/zenodo.18619641

GitHub
https://github.com/rhviana/gdcrLicense:CC BY 4.0Author:Ricardo Luz Holanda Viana
Independent Solo Researcher
Enterprise Integration Architect
SAP BTP Integration Suite Expert
SAP Press e-Bite Author — Enterprise Messaging, 2021
Creator of DEIP, SDIA, GDCR, DDCR, ODCP, EDCP, DDCP
ORCID: 0009-0009-9549-5862
www.domain-intent.comFinal StatementPhantom v12 is not trying to be the most abstract router possible.It is doing something more useful:Parse one semantic URL suffix.
Normalize one action.
Resolve one KVM route.
Build one deterministic target URL.
Expose one traceable integration contract.That is enough to replace endpoint sprawl with governed metadata.It is a reference template, not a universal plugin.It can be adapted.It can be optimized.It can be hardened.But the architectural principle remains stable:The proxy never changes for the route. The KVM grows.GDCR exposes the semantic facade.DDCR resolves the runtime route.ODCP governs the CPI layer behind it.Same domain intent.Same metadata discipline.Same architecture.That is SDIA in execution.Apendix A –  Phantom v12 – JavaScript Policy the DDCR – Domain Driven-Centric Router./**
* ============================================================================
* GDCR Routing Engine – Phantom v12 (PRODUCTION READY)
* ============================================================================
* Author: Ricardo Luz Holanda Viana
* Email: rhviana@gmail.com
* License: CC BY 4.0 (https://creativecommons.org/licenses/by/4.0/)
* DOI: https://doi.org/10.5281/zenodo.18619641
* GitHub: https://github.com/rhviana/deip
*
* Zero regex | Zero hardcode | Zero split | O(k) lookup after warm cache
* Compatible: SAP BTP APIM (Rhino) | Apigee | Kong (with adapter)
* ============================================================================
*
* KVM key format: dcrp<process><entity><actioncode><vendor>id<digits>
* KVM value format: <adapter>
* Example:
* Key: dcrpfinanceorderscsalesforceid01
* Value: http
*
* Path format: /<entity>/<action>/<vendor>[/<id>[/<extra>…]]
* ============================================================================
*/

// ── Canonical Action Map — Phantom v12 ──────────────────────────────────────
// 247 action variants → 15 canonical single-character codes
// Author: Ricardo Luz Holanda Viana
// DOI: 10.5281/zenodo.18582492

var GLOBAL_ACTION_MAP = {

// ── c: CREATE (27 variants) ──────────────────────────────────────────────
“create”:”c”, “created”:”c”, “post”:”c”,
“insert”:”c”, “add”:”c”, “new”:”c”,
“submit”:”c”, “register”:”c”, “registered”:”c”,
“provision”:”c”, “provisioning”:”c”,”onboard”:”c”,
“onboarding”:”c”,”publish”:”c”, “publishing”:”c”,
“generate”:”c”, “build”:”c”, “built”:”c”,
“open”:”c”, “opening”:”c”, “setup”:”c”,
“initialize”:”c”,”initializing”:”c”,”initialized”:”c”,
“draft”:”c”,

// ── r: READ (25 variants) ────────────────────────────────────────────────
“read”:”r”, “get”:”r”, “fetch”:”r”,
“retrieve”:”r”, “view”:”r”, “viewed”:”r”,
“list”:”r”, “listing”:”r”, “listed”:”r”,
“show”:”r”, “showing”:”r”, “shown”:”r”,
“search”:”r”, “searching”:”r”, “searched”:”r”,
“find”:”r”, “finding”:”r”, “found”:”r”,
“query”:”r”, “querying”:”r”, “queried”:”r”,
“lookup”:”r”, “check”:”r”, “inspect”:”r”,

// ── u: UPDATE (27 variants) ──────────────────────────────────────────────
“update”:”u”, “updating”:”u”, “updated”:”u”,
“put”:”u”, “patch”:”u”, “patching”:”u”,
“modify”:”u”, “modifying”:”u”, “modified”:”u”,
“change”:”u”, “changing”:”u”, “changed”:”u”,
“edit”:”u”, “editing”:”u”, “edited”:”u”,
“revise”:”u”, “revising”:”u”, “revised”:”u”,
“amend”:”u”, “amending”:”u”, “amended”:”u”,
“replace”:”u”, “replacing”:”u”, “replaced”:”u”,
“upsert”:”u”,

// ── d: DELETE (20 variants) ──────────────────────────────────────────────
“delete”:”d”, “deleting”:”d”, “deleted”:”d”,
“remove”:”d”, “removing”:”d”, “removed”:”d”,
“cancel”:”d”, “cancelling”:”d”, “cancelled”:”d”,
“terminate”:”d”, “terminating”:”d”,”terminated”:”d”,
“destroy”:”d”, “destroying”:”d”, “destroyed”:”d”,
“deactivate”:”d”,”purge”:”d”, “purging”:”d”,
“purged”:”d”, “discard”:”d”,

// ── s: SYNC (18 variants) ────────────────────────────────────────────────
“sync”:”s”, “syncing”:”s”, “synced”:”s”,
“synchronize”:”s”, “synchronizing”:”s”, “synchronized”:”s”,
“replicate”:”s”, “replicating”:”s”, “replicated”:”s”,
“mirror”:”s”, “mirroring”:”s”, “mirrored”:”s”,
“refresh”:”s”, “refreshing”:”s”, “refreshed”:”s”,
“reconcile”:”s”, “harmonize”:”s”, “align”:”s”,

// ── a: APPROVE (18 variants) ─────────────────────────────────────────────
“approve”:”a”, “approving”:”a”, “approved”:”a”,
“authorize”:”a”, “authorizing”:”a”, “authorized”:”a”,
“accept”:”a”, “accepting”:”a”, “accepted”:”a”,
“validate”:”a”, “validating”:”a”, “validated”:”a”,
“confirm”:”a”, “confirming”:”a”, “confirmed”:”a”,
“endorse”:”a”, “certify”:”a”, “signoff”:”a”,

// ── n: NOTIFY (18 variants) ──────────────────────────────────────────────
“notify”:”n”, “notifying”:”n”, “notified”:”n”,
“alert”:”n”, “alerting”:”n”, “alerted”:”n”,
“inform”:”n”, “informing”:”n”, “informed”:”n”,
“announce”:”n”, “announcing”:”n”, “announced”:”n”,
“broadcast”:”n”, “broadcasting”:”n”,”broadcasted”:”n”,
“emit”:”n”, “trigger”:”n”, “signal”:”n”,

// ── t: TRANSFER (16 variants) ────────────────────────────────────────────
“transfer”:”t”, “transferring”:”t”,”transferred”:”t”,
“send”:”t”, “sending”:”t”, “sent”:”t”,
“move”:”t”, “moving”:”t”, “moved”:”t”,
“migrate”:”t”, “migrating”:”t”, “migrated”:”t”,
“forward”:”t”, “forwarding”:”t”, “relay”:”t”,
“handoff”:”t”,

// ── e: ENABLE (12 variants) ──────────────────────────────────────────────
“enable”:”e”, “enabling”:”e”, “enabled”:”e”,
“activate”:”e”, “activating”:”e”, “activated”:”e”,
“start”:”e”, “starting”:”e”, “started”:”e”,
“resume”:”e”, “resuming”:”e”, “resumed”:”e”,

// ── b: DISABLE (12 variants) ─────────────────────────────────────────────
“disable”:”b”, “disabling”:”b”, “disabled”:”b”,
“deactivate”:”b”, “deactivating”:”b”,”deactivated”:”b”,
“stop”:”b”, “stopping”:”b”, “stopped”:”b”,
“pause”:”b”, “pausing”:”b”, “paused”:”b”,

// ── v: ARCHIVE (10 variants) ─────────────────────────────────────────────
“archive”:”v”, “archiving”:”v”, “archived”:”v”,
“store”:”v”, “storing”:”v”, “stored”:”v”,
“backup”:”v”, “backing”:”v”, “backed”:”v”,
“retain”:”v”,

// ── w: RESTORE (10 variants) ─────────────────────────────────────────────
“restore”:”w”, “restoring”:”w”, “restored”:”w”,
“unarchive”:”w”, “recover”:”w”, “recovering”:”w”,
“recovered”:”w”, “rollback”:”w”, “revert”:”w”,
“reinstate”:”w”,

// ── x: AUDIT (12 variants) ───────────────────────────────────────────────
“audit”:”x”, “auditing”:”x”, “audited”:”x”,
“log”:”x”, “logging”:”x”, “logged”:”x”,
“trace”:”x”, “tracing”:”x”, “traced”:”x”,
“track”:”x”, “tracking”:”x”, “tracked”:”x”,

// ── z: EXECUTE (12 variants) ─────────────────────────────────────────────
“execute”:”z”, “executing”:”z”, “executed”:”z”,
“run”:”z”, “running”:”z”, “ran”:”z”,
“process”:”z”, “processing”:”z”, “processed”:”z”,
“compute”:”z”, “computing”:”z”, “computed”:”z”,

// ── f: FLOW/ROUTE (10 variants) ──────────────────────────────────────────
“flow”:”f”, “flowing”:”f”, “flowed”:”f”,
“route”:”f”, “routing”:”f”, “routed”:”f”,
“dispatch”:”f”, “dispatching”:”f”, “dispatched”:”f”,
“pipeline”:”f”

};

// Validation
var entries = Object.keys(GLOBAL_ACTION_MAP);
var codes = {};
entries.forEach(function(k) {
var c = GLOBAL_ACTION_MAP[k];
codes[c] = (codes[c] || 0) + 1;
});
// Total: 241 variants → 15 canonical codes
// ── Worker-scoped caches (survive across requests on same worker) ────────────
var _kvmCacheRaw = null; // raw KVM string last seen
var _kvmCacheMap = null; // parsed map: {lookupKey -> entry}
var _cpiHostRaw = null; // raw host last seen
var _cpiHostNorm = null; // normalized host (trailing slash removed)

// ── Char code constants (zero string allocation on comparison) ───────────────
var CC_SLASH = 47; // ‘/’
var CC_UNDER = 95; // ‘_’
var CC_0 = 48; // ‘0’
var CC_9 = 57; // ‘9’
var CC_A_LOW = 97; // ‘a’
var CC_Z_LOW = 122; // ‘z’

// ============================================================================
// parseKvm — runs ONLY when KVM string changes (amortized O(n) per request)
//
// Strategy for process extraction (zero hardcode, zero regex):
// Key structure: dcrp + <process> + <entity> + <actioncode> + <vendor> + id<digits>
// We know: entity (from request path) — BUT at parse time we don’t have entity.
// Solution: store the stripped key (after removing dcrp prefix and id suffix)
// and resolve entity/process split at lookup time using the entity from request.
// This keeps parse O(n) over KVM entries and lookup O(k) over map keys (k<=30).
// ============================================================================
function parseKvm(kvmString) {
var map = {};
var len = kvmString.length;
var start = 0;

while (start < len) {

// ── Find entry boundaries ──────────────────────────────────────────────
var comma = kvmString.indexOf(‘,’, start);
if (comma === -1) comma = len;

var colon = kvmString.indexOf(‘:’, start);
if (colon === -1 || colon > comma) { start = comma + 1; continue; }

var keyRaw = kvmString.substring(start, colon);
var adapter = kvmString.substring(colon + 1, comma);

if (!keyRaw || !adapter) { start = comma + 1; continue; }

var keyLow = keyRaw.toLowerCase();

// ── Extract numeric ID from end of key (e.g. “id01” → “01”) ──────────
var idVal = ”;
var idPos = keyLow.lastIndexOf(‘id’);
if (idPos !== -1 && idPos < keyLow.length – 2) {
var dStr = ”;
for (var di = idPos + 2; di < keyLow.length; di++) {
var dc = keyLow.charCodeAt(di);
if (dc >= CC_0 && dc <= CC_9) dStr += keyLow.charAt(di);
else break;
}
if (dStr) idVal = dStr;
}

// ── Strip ‘dcrp’ prefix and ‘id<digits>’ suffix ───────────────────────
// Result: <process><entity><actioncode><vendor>
var stripped = keyLow.substring(4); // remove leading ‘dcrp’
if (idVal) {
// remove trailing ‘id’ + digits
stripped = stripped.substring(0, stripped.length – (2 + idVal.length));
}

// ── Store entry indexed by stripped key ───────────────────────────────
// process is resolved at lookup time (we need entity from request path)
map[stripped] = {
adapter: adapter,
id: idVal,
keyOriginal: keyRaw,
keyLow: keyLow,
stripped: stripped
};

start = comma + 1;
}
return map;
}

// ============================================================================
// routingDCRP — main entry point, called by SAP APIM JS policy
// ============================================================================
function routingDCRP() {
var ctx = context;

// ── Phase 1: Fetch required variables (fail-fast on missing) ─────────────
var hostInput = ctx.getVariable(‘target.cpi.host’);
var kvmInput = ctx.getVariable(‘kvm.idinterface’);
var pathInput = ctx.getVariable(‘proxy.pathsuffix’);

if (!hostInput || !kvmInput || !pathInput) {
ctx.setVariable(‘dcrp.routing.success’, ‘false’);
ctx.setVariable(‘dcrp.routing.error’,
‘Missing:’ +
(hostInput ? ” : ‘ [target.cpi.host]’) +
(kvmInput ? ” : ‘ [kvm.idinterface]’) +
(pathInput ? ” : ‘ [proxy.pathsuffix]’)
);
return;
}

// ── Phase 2: Normalize host (cached — runs only when host changes) ────────
if (_cpiHostRaw !== hostInput) {
_cpiHostRaw = hostInput;
var hLen = hostInput.length;
_cpiHostNorm = (hLen > 0 && hostInput.charCodeAt(hLen – 1) === CC_SLASH)
? hostInput.substring(0, hLen – 1)
: hostInput;
}
var host = _cpiHostNorm;

// ── Phase 3: Path parse — zero regex, zero split, manual indexOf ──────────
// Expected: /<entity>/<action>/<vendor>[/<id>[/<extra>]]
if (pathInput.charCodeAt(0) !== CC_SLASH) {
ctx.setVariable(‘dcrp.routing.success’, ‘false’);
ctx.setVariable(‘dcrp.routing.error’, ‘Path must start with /’);
return;
}

var s1 = pathInput.indexOf(‘/’, 1);
if (s1 === -1) {
ctx.setVariable(‘dcrp.routing.success’, ‘false’);
ctx.setVariable(‘dcrp.routing.error’, ‘Missing entity segment’);
return;
}

var s2 = pathInput.indexOf(‘/’, s1 + 1);
if (s2 === -1) {
ctx.setVariable(‘dcrp.routing.success’, ‘false’);
ctx.setVariable(‘dcrp.routing.error’, ‘Missing action segment’);
return;
}

// ── FIX: s3 === -1 is valid — vendor is the last path segment ────────────
var s3 = pathInput.indexOf(‘/’, s2 + 1);

var entity = pathInput.substring(1, s1).toLowerCase();
var action = pathInput.substring(s1 + 1, s2).toLowerCase();
var vendor = (s3 === -1)
? pathInput.substring(s2 + 1)
: pathInput.substring(s2 + 1, s3);
vendor = vendor.toLowerCase();

// ── FIX: strip optional leading underscore — was vendor.substring(n) ─────
if (vendor.length > 0 && vendor.charCodeAt(0) === CC_UNDER) {
vendor = vendor.substring(1);
}

// Optional: id and extra path segments (only meaningful when s3 exists)
var id = ”;
var extraPath = ”;
if (s3 !== -1) {
var s4 = pathInput.indexOf(‘/’, s3 + 1);
id = (s4 === -1) ? pathInput.substring(s3 + 1) : pathInput.substring(s3 + 1, s4);
extraPath = (s4 === -1) ? ” : pathInput.substring(s4);
}

if (!entity || !action || !vendor) {
ctx.setVariable(‘dcrp.routing.success’, ‘false’);
ctx.setVariable(‘dcrp.routing.error’, ‘Empty path segment’);
return;
}

// ── Phase 4: Action normalization — O(n) hash lookup, char fallback ───────
var actionCode = GLOBAL_ACTION_MAP[action];
if (!actionCode) {
var c0 = action.charCodeAt(0);
actionCode = (c0 >= CC_A_LOW && c0 <= CC_Z_LOW) ? action.charAt(0) : ‘x’;
}

// ── Phase 5: KVM cache — rebuild only when raw string changes ─────────────
if (_kvmCacheRaw !== kvmInput) {
_kvmCacheRaw = kvmInput;
_kvmCacheMap = parseKvm(kvmInput);
}

// ── Phase 6: Lookup — O(k) over map, k <= 30, ~0.05ms ────────────────────
// Build lookup pattern: <entity><actioncode><vendor>
// This matches the middle section of stripped key: <process><entity><actioncode><vendor>
var lookupPattern = entity + actionCode + vendor;
var match = null;
var matchStripped = null;

var mapKeys = Object.keys(_kvmCacheMap);
for (var mi = 0; mi < mapKeys.length; mi++) {
var mk = mapKeys[mi];
if (mk.indexOf(lookupPattern) !== -1) {
match = _kvmCacheMap[mk];
matchStripped = mk;
break;
}
}

if (!match) {
ctx.setVariable(‘dcrp.routing.success’, ‘false’);
ctx.setVariable(‘dcrp.routing.error’, ‘No route for: ‘ + lookupPattern);
return;
}

// ── Phase 7: Process extraction — fully dynamic, zero hardcode ───────────
// stripped = <process><entity><actioncode><vendor>
// We know entity from request path → find its position → process = everything before it
// When eIdx === 0 there is no process prefix — domain-only key, process is empty
var process = ”;
var eIdx = matchStripped.indexOf(entity);
if (eIdx > 0) {
process = matchStripped.substring(0, eIdx);
}

// ── Phase 8: Build target URL ─────────────────────────────────────────────
// Skip process segment when empty to avoid duplication in URL
var url = host + ‘/’ + match.adapter + ‘/dcrp’;
if (process) url += ‘/’ + process;
url += ‘/’ + entity + ‘/’ + actionCode;

if (match.id) url += ‘/id’ + match.id;
if (id) url += ‘/’ + id;
if (extraPath) url += extraPath;

var qs = ctx.getVariable(‘request.querystring’);
if (qs) url += ‘?’ + qs;

// ── Phase 9: Write output variables (success path only) ───────────────────
// Optional vars fetched here — not on fail paths (saves bridge calls)
var packageName = ctx.getVariable(‘kvm.packagename’) || ‘unknown’;
var sapProcess = ctx.getVariable(‘kvm.sapprocess’) || ‘unknown’;
var messageId = ctx.getVariable(‘messageid’) || ”;

ctx.setVariable(‘dcrp.routing.success’, ‘true’);
ctx.setVariable(‘target.url’, url);
ctx.setVariable(‘request.header.x-dcrp-process’, process);
ctx.setVariable(‘request.header.x-dcrp-adapter’, match.adapter);
ctx.setVariable(‘request.header.x-dcrp-version’, ‘12.0’);
ctx.setVariable(‘request.header.x-dcrp-key’, match.keyOriginal);
ctx.setVariable(‘request.header.x-packagename’, packageName);
ctx.setVariable(‘request.header.x-sapprocess’, sapProcess);
ctx.setVariable(‘request.header.x-senderid’, vendor);
ctx.setVariable(‘request.header.x-correlationid’, messageId);
ctx.setVariable(‘request.header.x-idinterface’, match.id);
}

// ============================================================================
// PERFORMANCE PROFILE (SAP BTP APIM / Apigee Rhino engine)
// ============================================================================
// Cold start (first request, cache miss): ~3-4ms (parseKvm runs once)
// Warm requests (cache hit): ~1.5-2.5ms
// KVM size supported: up to ~200 entries without degradation
// Memory per worker: <5KB (map + strings)
// Regex operations: ZERO
// Array split operations: ZERO
// Hardcoded entity/process patterns: ZERO
// Object allocations per warm request: ~3 (url string, mapKeys, loop var)
// ============================================================================    Read More Technology Blog Posts by Members articles 

#SAP

#SAPTechnologyblog

You May Also Like

More From Author