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