@open-rap/compiler 0.0.0-stage → 0.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +21 -2
- package/dist/agent/server.d.ts +35 -0
- package/dist/agent/server.js +408 -0
- package/dist/agent/server.js.map +1 -0
- package/dist/authoring/abapgit.d.ts +38 -0
- package/dist/authoring/abapgit.js +242 -0
- package/dist/authoring/abapgit.js.map +1 -0
- package/dist/authoring/catalog.d.ts +48 -0
- package/dist/authoring/catalog.js +117 -0
- package/dist/authoring/catalog.js.map +1 -0
- package/dist/authoring/floorplan.d.ts +28 -0
- package/dist/authoring/floorplan.js +31 -0
- package/dist/authoring/floorplan.js.map +1 -0
- package/dist/authoring/generate.d.ts +57 -0
- package/dist/authoring/generate.js +630 -0
- package/dist/authoring/generate.js.map +1 -0
- package/dist/authoring/mcp.d.ts +15 -0
- package/dist/authoring/mcp.js +52 -0
- package/dist/authoring/mcp.js.map +1 -0
- package/dist/authoring/new.d.ts +35 -0
- package/dist/authoring/new.js +389 -0
- package/dist/authoring/new.js.map +1 -0
- package/dist/bdef/ast.d.ts +249 -0
- package/dist/bdef/ast.js +3 -0
- package/dist/bdef/ast.js.map +1 -0
- package/dist/bdef/lexer.d.ts +105 -0
- package/dist/bdef/lexer.js +148 -0
- package/dist/bdef/lexer.js.map +1 -0
- package/dist/bdef/parser.d.ts +22 -0
- package/dist/bdef/parser.js +1012 -0
- package/dist/bdef/parser.js.map +1 -0
- package/dist/build.d.ts +26 -0
- package/dist/build.js +62 -0
- package/dist/build.js.map +1 -0
- package/dist/cds/adapter.d.ts +91 -0
- package/dist/cds/adapter.js +381 -0
- package/dist/cds/adapter.js.map +1 -0
- package/dist/cds/expression-ast.d.ts +65 -0
- package/dist/cds/expression-ast.js +330 -0
- package/dist/cds/expression-ast.js.map +1 -0
- package/dist/cli.d.ts +9 -0
- package/dist/cli.js +485 -0
- package/dist/cli.js.map +1 -0
- package/dist/dcl/parser.d.ts +59 -0
- package/dist/dcl/parser.js +338 -0
- package/dist/dcl/parser.js.map +1 -0
- package/dist/ddic/catalog.d.ts +17 -0
- package/dist/ddic/catalog.js +211 -0
- package/dist/ddic/catalog.js.map +1 -0
- package/dist/ddic/cds-types.d.ts +47 -0
- package/dist/ddic/cds-types.js +131 -0
- package/dist/ddic/cds-types.js.map +1 -0
- package/dist/ddic/table-source.d.ts +70 -0
- package/dist/ddic/table-source.js +500 -0
- package/dist/ddic/table-source.js.map +1 -0
- package/dist/ddic/xml.d.ts +65 -0
- package/dist/ddic/xml.js +201 -0
- package/dist/ddic/xml.js.map +1 -0
- package/dist/dev.d.ts +128 -0
- package/dist/dev.js +434 -0
- package/dist/dev.js.map +1 -0
- package/dist/diagnostics.d.ts +33 -0
- package/dist/diagnostics.js +53 -0
- package/dist/diagnostics.js.map +1 -0
- package/dist/gen/abap/derived-types.d.ts +26 -0
- package/dist/gen/abap/derived-types.js +323 -0
- package/dist/gen/abap/derived-types.js.map +1 -0
- package/dist/gen/abap/eml.d.ts +118 -0
- package/dist/gen/abap/eml.js +362 -0
- package/dist/gen/abap/eml.js.map +1 -0
- package/dist/gen/abap/enums.d.ts +13 -0
- package/dist/gen/abap/enums.js +74 -0
- package/dist/gen/abap/enums.js.map +1 -0
- package/dist/gen/abap/framework-http.d.ts +14 -0
- package/dist/gen/abap/framework-http.js +1049 -0
- package/dist/gen/abap/framework-http.js.map +1 -0
- package/dist/gen/abap/framework-query.d.ts +14 -0
- package/dist/gen/abap/framework-query.js +225 -0
- package/dist/gen/abap/framework-query.js.map +1 -0
- package/dist/gen/abap/framework-utils.d.ts +29 -0
- package/dist/gen/abap/framework-utils.js +3707 -0
- package/dist/gen/abap/framework-utils.js.map +1 -0
- package/dist/gen/abap/framework.d.ts +12 -0
- package/dist/gen/abap/framework.js +480 -0
- package/dist/gen/abap/framework.js.map +1 -0
- package/dist/gen/abap/generate.d.ts +40 -0
- package/dist/gen/abap/generate.js +238 -0
- package/dist/gen/abap/generate.js.map +1 -0
- package/dist/gen/abap/rewrite.d.ts +82 -0
- package/dist/gen/abap/rewrite.js +957 -0
- package/dist/gen/abap/rewrite.js.map +1 -0
- package/dist/gen/abap/shadow.d.ts +25 -0
- package/dist/gen/abap/shadow.js +175 -0
- package/dist/gen/abap/shadow.js.map +1 -0
- package/dist/gen/abap/tokens.d.ts +33 -0
- package/dist/gen/abap/tokens.js +87 -0
- package/dist/gen/abap/tokens.js.map +1 -0
- package/dist/gen/app/index.d.ts +25 -0
- package/dist/gen/app/index.js +322 -0
- package/dist/gen/app/index.js.map +1 -0
- package/dist/gen/csdl.d.ts +11 -0
- package/dist/gen/csdl.js +562 -0
- package/dist/gen/csdl.js.map +1 -0
- package/dist/gen/deploy.d.ts +12 -0
- package/dist/gen/deploy.js +53 -0
- package/dist/gen/deploy.js.map +1 -0
- package/dist/gen/docs/index.d.ts +35 -0
- package/dist/gen/docs/index.js +267 -0
- package/dist/gen/docs/index.js.map +1 -0
- package/dist/gen/docs/markdown.d.ts +23 -0
- package/dist/gen/docs/markdown.js +187 -0
- package/dist/gen/docs/markdown.js.map +1 -0
- package/dist/gen/docs/site.d.ts +17 -0
- package/dist/gen/docs/site.js +106 -0
- package/dist/gen/docs/site.js.map +1 -0
- package/dist/gen/keycloak.d.ts +21 -0
- package/dist/gen/keycloak.js +90 -0
- package/dist/gen/keycloak.js.map +1 -0
- package/dist/gen/launchpad/index.d.ts +48 -0
- package/dist/gen/launchpad/index.js +302 -0
- package/dist/gen/launchpad/index.js.map +1 -0
- package/dist/gen/launchpad/ui.d.ts +14 -0
- package/dist/gen/launchpad/ui.js +350 -0
- package/dist/gen/launchpad/ui.js.map +1 -0
- package/dist/gen/mcp.d.ts +23 -0
- package/dist/gen/mcp.js +244 -0
- package/dist/gen/mcp.js.map +1 -0
- package/dist/gen/sql-expressions.d.ts +12 -0
- package/dist/gen/sql-expressions.js +136 -0
- package/dist/gen/sql-expressions.js.map +1 -0
- package/dist/gen/sql.d.ts +38 -0
- package/dist/gen/sql.js +612 -0
- package/dist/gen/sql.js.map +1 -0
- package/dist/gen/ui/index.d.ts +7 -0
- package/dist/gen/ui/index.js +27 -0
- package/dist/gen/ui/index.js.map +1 -0
- package/dist/gen/ui/layout.d.ts +230 -0
- package/dist/gen/ui/layout.js +511 -0
- package/dist/gen/ui/layout.js.map +1 -0
- package/dist/gen/ui/static.d.ts +55 -0
- package/dist/gen/ui/static.js +2901 -0
- package/dist/gen/ui/static.js.map +1 -0
- package/dist/gen/ui/views.d.ts +10 -0
- package/dist/gen/ui/views.js +511 -0
- package/dist/gen/ui/views.js.map +1 -0
- package/dist/gen/vendor.d.ts +23 -0
- package/dist/gen/vendor.js +68 -0
- package/dist/gen/vendor.js.map +1 -0
- package/dist/gen/xml.d.ts +9 -0
- package/dist/gen/xml.js +42 -0
- package/dist/gen/xml.js.map +1 -0
- package/dist/index.d.ts +20 -0
- package/dist/index.js +20 -0
- package/dist/index.js.map +1 -0
- package/dist/input/project.d.ts +50 -0
- package/dist/input/project.js +128 -0
- package/dist/input/project.js.map +1 -0
- package/dist/ir/access-controls.d.ts +11 -0
- package/dist/ir/access-controls.js +148 -0
- package/dist/ir/access-controls.js.map +1 -0
- package/dist/ir/authorizations.d.ts +25 -0
- package/dist/ir/authorizations.js +178 -0
- package/dist/ir/authorizations.js.map +1 -0
- package/dist/ir/behaviors.d.ts +16 -0
- package/dist/ir/behaviors.js +548 -0
- package/dist/ir/behaviors.js.map +1 -0
- package/dist/ir/builder.d.ts +20 -0
- package/dist/ir/builder.js +505 -0
- package/dist/ir/builder.js.map +1 -0
- package/dist/ir/config.d.ts +25 -0
- package/dist/ir/config.js +130 -0
- package/dist/ir/config.js.map +1 -0
- package/dist/ir/dictionary.d.ts +54 -0
- package/dist/ir/dictionary.js +328 -0
- package/dist/ir/dictionary.js.map +1 -0
- package/dist/ir/entities.d.ts +58 -0
- package/dist/ir/entities.js +676 -0
- package/dist/ir/entities.js.map +1 -0
- package/dist/ir/expressions.d.ts +33 -0
- package/dist/ir/expressions.js +327 -0
- package/dist/ir/expressions.js.map +1 -0
- package/dist/ir/handlers.d.ts +63 -0
- package/dist/ir/handlers.js +398 -0
- package/dist/ir/handlers.js.map +1 -0
- package/dist/ir/number-ranges.d.ts +24 -0
- package/dist/ir/number-ranges.js +74 -0
- package/dist/ir/number-ranges.js.map +1 -0
- package/dist/ir/seed.d.ts +23 -0
- package/dist/ir/seed.js +78 -0
- package/dist/ir/seed.js.map +1 -0
- package/dist/ir/service.d.ts +21 -0
- package/dist/ir/service.js +96 -0
- package/dist/ir/service.js.map +1 -0
- package/dist/launchpad/config.d.ts +47 -0
- package/dist/launchpad/config.js +321 -0
- package/dist/launchpad/config.js.map +1 -0
- package/dist/launchpad/workspace.d.ts +48 -0
- package/dist/launchpad/workspace.js +166 -0
- package/dist/launchpad/workspace.js.map +1 -0
- package/dist/srvd/parser.d.ts +36 -0
- package/dist/srvd/parser.js +193 -0
- package/dist/srvd/parser.js.map +1 -0
- package/dist/standard/data.d.ts +18 -0
- package/dist/standard/data.js +534 -0
- package/dist/standard/data.js.map +1 -0
- package/dist/standard/index.d.ts +36 -0
- package/dist/standard/index.js +507 -0
- package/dist/standard/index.js.map +1 -0
- package/dist/version.d.ts +2 -0
- package/dist/version.js +4 -0
- package/dist/version.js.map +1 -0
- package/docs/AGENTS.md +79 -0
- package/docs/ANNOTATIONS.md +76 -0
- package/docs/DECISIONS.md +1554 -0
- package/docs/DEPLOYMENT.md +113 -0
- package/docs/DIAGNOSTICS.md +116 -0
- package/docs/FEATURES.md +287 -0
- package/docs/GUIDE.md +560 -0
- package/docs/KNOWN_GAPS.md +216 -0
- package/docs/MCP.md +84 -0
- package/docs/PLAN.md +690 -0
- package/docs/README.md +148 -0
- package/docs/REMOTE_APIS.md +121 -0
- package/docs/ROADMAP.md +35 -0
- package/docs/UTILITIES.md +95 -0
- package/package.json +49 -3
package/docs/PLAN.md
ADDED
|
@@ -0,0 +1,690 @@
|
|
|
1
|
+
# RAP compiler → standalone Node.js application — Plan
|
|
2
|
+
|
|
3
|
+
> **Status: approved on 2026-11-09, being implemented.** The decisions taken are in §11 and
|
|
4
|
+
> detailed in [docs/DECISIONS.md](docs/DECISIONS.md). The scope was widened to all the features
|
|
5
|
+
> of a non-draft managed RAP BO (decision D14), then to draft (D54); the coverage is tracked in
|
|
6
|
+
> [docs/FEATURES.md](docs/FEATURES.md). This plan is the original design document: the
|
|
7
|
+
> milestones and choices it describes are the starting point, the decisions record what changed.
|
|
8
|
+
> The findings of §2 come from checks made on 2026-11-09 on the published versions of the
|
|
9
|
+
> libraries, in a sandbox outside the repository.
|
|
10
|
+
|
|
11
|
+
## Contents
|
|
12
|
+
|
|
13
|
+
1. [Summary](#1-summary)
|
|
14
|
+
2. [Preliminary checks](#2-preliminary-checks)
|
|
15
|
+
3. [Architecture](#3-architecture)
|
|
16
|
+
4. [Technical choices and licenses](#4-technical-choices-and-licenses)
|
|
17
|
+
5. [Inputs, outputs, CLI](#5-inputs-outputs-cli)
|
|
18
|
+
6. [Detailed design](#6-detailed-design)
|
|
19
|
+
7. [Test strategy](#7-test-strategy)
|
|
20
|
+
8. [Milestones](#8-milestones)
|
|
21
|
+
9. [Risks](#9-risks)
|
|
22
|
+
10. [Travel / Booking fixture](#10-travel--booking-fixture)
|
|
23
|
+
11. [Decisions taken](#11-decisions-taken)
|
|
24
|
+
12. [Appendix A — requests of the conformance suite](#appendix-a--requests-of-the-conformance-suite)
|
|
25
|
+
|
|
26
|
+
---
|
|
27
|
+
|
|
28
|
+
## 1. Summary
|
|
29
|
+
|
|
30
|
+
- The `rapt` CLI (strict TypeScript, ESM, Node ≥ 22) reads an **abapGit** export (TABL, DDLS,
|
|
31
|
+
DDLX, BDEF, SRVD, CLAS) and generates an application folder: OData V4 on Fastify, PostgreSQL
|
|
32
|
+
through Kysely, behavior pool transpiled by `@abaplint/transpiler`, freestyle OpenUI5 UI,
|
|
33
|
+
Dockerfile and docker-compose.
|
|
34
|
+
- The generated application bundles a **generic runtime** driven by the serialized IR
|
|
35
|
+
(`model.json`). Only what has to be is generated: SQL, CSDL, JavaScript of the ABP, UI,
|
|
36
|
+
packaging. The runtime is thus tested once for all BOs.
|
|
37
|
+
- **Hard point, de-risked.** abaplint types the RAP derived types (`TYPE TABLE FOR …`, `keys`,
|
|
38
|
+
`failed`, `reported`, `result`) as `void`, and the transpiler then refuses the ABP. Proposed
|
|
39
|
+
solution: generate these types in ABAP from the IR, then rewrite the ABP before transpilation
|
|
40
|
+
(declarations of the handlers, `#` of the EML operands, inline `DATA(…)` of the EML responses).
|
|
41
|
+
**A PoC validates it**: a validation and an action run end to end.
|
|
42
|
+
- **Good news.** Transpiler 2.14 already lowers `READ/MODIFY/COMMIT/ROLLBACK ENTITIES` to a
|
|
43
|
+
"kernel" class `KERNEL_RAP_EML`, injected at runtime. Our EML layer consists in implementing
|
|
44
|
+
this contract in TypeScript, on top of the transactional buffer.
|
|
45
|
+
- No SAP dependency: no `@sap/cds`, no SAPUI5, no `sap.fe`. All the licenses are MIT,
|
|
46
|
+
Apache-2.0, BSD or PostgreSQL (§4).
|
|
47
|
+
- The 13 decisions of §11 are taken (details and reasons in docs/DECISIONS.md).
|
|
48
|
+
|
|
49
|
+
---
|
|
50
|
+
|
|
51
|
+
## 2. Preliminary checks
|
|
52
|
+
|
|
53
|
+
Tested versions: `@abaplint/core` 2.120.98, `@abaplint/transpiler` and `@abaplint/runtime`
|
|
54
|
+
2.14.24, Chevrotain 13.2, Node 22.23, pnpm 10.28.
|
|
55
|
+
|
|
56
|
+
| # | Subject | Finding | Consequence for the plan |
|
|
57
|
+
|---|---|---|---|
|
|
58
|
+
| V1 | CDS (`CDSParser` of abaplint) | Parses without errors `define root view entity … composition [0..*] of …`, `association to parent … on $projection.X = _P.X`, `as projection on … provider contract transactional_query`, `redirected to composition child`, and the DDLX `annotate entity … with { … }`. The annotations come out as a tree (objects, arrays, `#X` enumerations, strings, booleans). | No dedicated CDS parser for the MVP: an abaplint AST → IR adapter is enough. A complementary parser only if a gap shows up. |
|
|
59
|
+
| V2 | SRVD | Object recognized, but source not parsed. | Small Chevrotain grammar (`define service … { expose … as …; }`). |
|
|
60
|
+
| V3 | BDEF in abaplint | A combinator parser exists (≈ 30 expressions). It produces a generic tree abaplint mostly uses to resolve aliases. | We write the requested Chevrotain grammar (typed AST, precise errors). The abaplint parser is used as an **oracle** in the tests. |
|
|
61
|
+
| V4 | Derived types | `TYPE TABLE FOR …` gives a table of `void` (internal marker "RAP-TODO"); `keys`, `failed`, `reported`, `mapped`, `result` are `void`. The transpiler stops on `WITH CORRESPONDING #( keys )`: `TypeNameOrInfer, type not found: #`. | We must provide these types ourselves (§6.3). |
|
|
62
|
+
| V5 | EML in the transpiler | `READ/MODIFY/COMMIT/ROLLBACK ENTITIES` become calls `abap.Classes['KERNEL_RAP_EML'].begin/read/modify/execute/commit/rollback`. The names are passed as strings (`bdef`, `entity`, `operation`, `fields`…); the tables and the targets `MAPPED/FAILED/REPORTED/RESULT` are passed as is. The upstream tests inject the kernel with `abap.Classes["KERNEL_RAP_EML"] = …`. | The EML layer is a TypeScript implementation of this contract. |
|
|
63
|
+
| V6 | EML not lowered | `READ … BY \_assoc`, `CREATE BY \_assoc`, `LINK`, `OPERATIONS`, `RELATING TO`, `AUGMENTING`, functions, `IN SIMULATION MODE`: the generated code raises a "not supported" error at runtime. | KNOWN_GAPS.md entries, with a diagnostic at compile time. The fixture does not use these forms. Upstream contribution possible (§11). |
|
|
64
|
+
| V7 | `IN LOCAL MODE`, `PRIVILEGED` | Ignored by the lowering: the kernel does not know whether the call is local. | All the EML coming from the ABP is treated as `IN LOCAL MODE`. No effect in the MVP scope (no authorizations nor dynamic feature control). Documented. |
|
|
65
|
+
| V8 | SQL layer of the transpiler | The transpiler builds SQL views from the DDLS and fails on the associations (`unsupported database definition`). | The registry given to the transpiler receives "shadow" DDLS, without associations. Our SQL comes from the IR, not from the transpiler. |
|
|
66
|
+
| V9 | `%…` components | `%tky`, `%control`, `%param`, `%msg`, `%element` and `INCLUDE TYPE … AS %tky` (named, nested groups) pass the abaplint syntax check, the transpilation and the execution. | The derived types can be written in standard ABAP. |
|
|
67
|
+
| V10 | **End-to-end PoC** | ABP rewritten by hand as the tool would, generated type interface, in-memory JS kernel. `validateDates` fills `failed` and `reported` (`%msg` with its text, `%element-EndDate = mk-on`). `acceptTravel` chains `MODIFY … IN LOCAL MODE UPDATE FIELDS`, `READ … ALL FIELDS`, then fills `result` with `%param`. The methods in the `PRIVATE SECTION` are called through the "friends" mechanism of the generated code. | The approach of §6.3 is retained. |
|
|
68
|
+
| V11 | Existing work | **open-steamgate** (MIT) emulates a complete ABAP application server (ADT, OData V2/V4, RAP runtime written in ABAP). Its documentation describes the same `KERNEL_RAP_EML` contract and the same typing limit: the EML targets must be declared explicitly, and the project relies on patched builds of abaplint and of the transpiler. | Good source of inspiration. Our approach works with the published packages, without a fork. |
|
|
69
|
+
| V12 | Semantics of `field ( mandatory )` | The ABAP Cloud documentation says it: no check at runtime, it is a hint for the consumer. | We reproduce this behavior: no runtime check, `Common.FieldControl` Mandatory in the CSDL. The business check stays in a validation. |
|
|
70
|
+
| V13 | Environment | PostgreSQL 16 binaries present. Docker CLI present, daemon stopped. Chromium and Playwright pre-installed. | Fast tests on PGlite, e2e on a real PostgreSQL. Docker in CI, or here if the daemon starts. |
|
|
71
|
+
|
|
72
|
+
---
|
|
73
|
+
|
|
74
|
+
## 3. Architecture
|
|
75
|
+
|
|
76
|
+
```
|
|
77
|
+
abapGit folder (fixtures/travel/src)
|
|
78
|
+
TABL .xml · DDLS .asddls · DDLX .asddlxs · BDEF .asbdef · SRVD .srvdsrv · CLAS .abap
|
|
79
|
+
│
|
|
80
|
+
▼
|
|
81
|
+
┌──────────────────────────── @open-rap/compiler ────────────────────────────┐
|
|
82
|
+
│ 1. Parsing │
|
|
83
|
+
│ abaplint registry: CDS (DDLS, DDLX), DDIC (TABL, DTEL, DOMA) + std catalog │
|
|
84
|
+
│ Chevrotain : BDEF (behavior + projection), SRVD │
|
|
85
|
+
│ 2. IR (model.json) + semantic validation (diagnostics file:line:col) │
|
|
86
|
+
│ 3. Generators │
|
|
87
|
+
│ sql/ PostgreSQL DDL: tables + views (R layer then C layer) │
|
|
88
|
+
│ csdl/ $metadata XML + UI, Common, Core, Capabilities, Measures annotations │
|
|
89
|
+
│ abap/ derived types → ABP rewrite → @abaplint/transpiler → .mjs modules │
|
|
90
|
+
│ ui/ OpenUI5 application (List Report / Object Page, freestyle) │
|
|
91
|
+
│ app/ package.json · server.mjs · Dockerfile · docker-compose · .env │
|
|
92
|
+
└───────────────────────────────────────────────────────────────────────────────────┘
|
|
93
|
+
│
|
|
94
|
+
▼
|
|
95
|
+
output folder (standalone application) + bundled @open-rap/runtime (vendor/)
|
|
96
|
+
|
|
97
|
+
browser (OpenUI5, ODataModel V4)
|
|
98
|
+
│ $batch, GET, POST, PATCH, DELETE
|
|
99
|
+
▼
|
|
100
|
+
OData V4 (Fastify) ── OIDC auth / dev mode ── CSRF token
|
|
101
|
+
│ reads │ writes, actions
|
|
102
|
+
▼ ▼
|
|
103
|
+
SQL views zc_* RAP managed runtime (buffer, triggers, save sequence)
|
|
104
|
+
│ │ ▲
|
|
105
|
+
│ │ └── KERNEL_RAP_EML ◄── transpiled ABP (@abaplint/runtime)
|
|
106
|
+
▼ ▼
|
|
107
|
+
PostgreSQL (Kysely + pg)
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
pnpm monorepo:
|
|
111
|
+
|
|
112
|
+
```
|
|
113
|
+
packages/
|
|
114
|
+
model/ @open-rap/model types of the IR, (de)serialization, version of model.json
|
|
115
|
+
compiler/ @open-rap/compiler rapt CLI, parsers, IR construction, generators
|
|
116
|
+
runtime/ @open-rap/runtime OData V4, RAP managed runtime, EML kernel, auth, DB access
|
|
117
|
+
fixtures/travel/ reference BO (abapGit format)
|
|
118
|
+
e2e/ end-to-end scenarios (HTTP + Playwright)
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
Internal dependencies: `compiler` → `model` ← `runtime`. The compiler only depends on the
|
|
122
|
+
runtime to bundle it into the output.
|
|
123
|
+
|
|
124
|
+
---
|
|
125
|
+
|
|
126
|
+
## 4. Technical choices and licenses
|
|
127
|
+
|
|
128
|
+
Licenses read on the npm registry on 2026-11-09. LICENSES.md is created in M1 and kept up to
|
|
129
|
+
date with each new dependency (name, version, license, use, date of the check).
|
|
130
|
+
|
|
131
|
+
| Building block | Choice | License | Reason, alternative |
|
|
132
|
+
|---|---|---|---|
|
|
133
|
+
| Language, tooling | TypeScript 7.0 (`strict`, `noUncheckedIndexedAccess`, `exactOptionalPropertyTypes`), ESM, Node ≥ 22, pnpm 10 (workspaces), Vitest 5 | Apache-2.0, MIT, MIT | Required by the specification. Fallback to TypeScript 5.9 if a tool is not yet compatible with 7. |
|
|
134
|
+
| ABAP and CDS parsing | `@abaplint/core` 2.120.98 | MIT | Required; sufficient CDS coverage (V1). Exact version pinned. |
|
|
135
|
+
| Transpilation of the ABP | `@abaplint/transpiler` and `@abaplint/runtime` 2.14.24 | MIT | Required. Same exact version for both, pinned. |
|
|
136
|
+
| BDEF, SRVD grammars, OData options | Chevrotain 13 | Apache-2.0 | Required for BDEF; reused for SRVD and `$filter`/`$orderby`/`$expand`. |
|
|
137
|
+
| HTTP server | Fastify 5 | MIT | `inject()` to test without a network, encapsulated plugins, good typing. Alternative: Express 5 (MIT). |
|
|
138
|
+
| Database | PostgreSQL 16+, `pg` 8, Kysely 0.29 | PostgreSQL, MIT, MIT | Required. Parameterized queries only. |
|
|
139
|
+
| Database for the tests | PGlite 0.5 + `kysely-pglite-dialect` 1.4 | Apache-2.0, MIT | Real in-process PostgreSQL engine (WASM), without Docker: fast integration tests. |
|
|
140
|
+
| OIDC | `jose` 6 (JWT, JWKS), `openid-client` 6 (code + PKCE), `@fastify/secure-session` 8 | MIT | Generic OIDC provider (Keycloak, Entra ID, Auth0…). |
|
|
141
|
+
| Static files | `@fastify/static` 10 | MIT | Serves the built UI. |
|
|
142
|
+
| OIDC tests | `oauth2-mock-server` 9 (dev); Keycloak (Docker image) | MIT, Apache-2.0 | Fake provider in Vitest; real Keycloak in e2e. |
|
|
143
|
+
| UI | OpenUI5 1.136 LTS: `sap.m`, `sap.f`, `sap.uxap`, `sap.ui.layout`, `sap.ui.core`, `sap_horizon` theme | Apache-2.0 | Allowed. The ODataModel V4 is the same as the one of Fiori Elements V4. `sap.fe`, `sap.ui.comp` and `sap.suite` are not part of OpenUI5 and are not used. |
|
|
144
|
+
| UI build | UI5 Tooling (`@ui5/cli` 4) | Apache-2.0 | `ui5 build self-contained`: standalone bundle, usable offline. |
|
|
145
|
+
| Browser E2E | Playwright 1.65 | Apache-2.0 | Chromium already installed here. |
|
|
146
|
+
| XML | in-house writing for the CSDL; `fast-xml-parser` 5 in dev | MIT | No runtime dependency for the XML. |
|
|
147
|
+
| CLI | `node:util` `parseArgs` | (Node, MIT) | No dependency. |
|
|
148
|
+
| Docker images | `node:22-alpine`, `postgres:16-alpine`, `quay.io/keycloak/keycloak` | MIT and free packages, PostgreSQL, Apache-2.0 | Official images. |
|
|
149
|
+
| Test corpus (option) | BDEF/CDS sources of the SAP-samples examples (refscen-flight, rap-opensap, fiori-feature-showcase) | Apache-2.0 | Robustness of the parsers, read-only, with attribution (decision 13). |
|
|
150
|
+
|
|
151
|
+
Prohibitions respected: no `@sap/cds` (CAP), no SAPUI5, no `sap.fe`, no library under the SAP
|
|
152
|
+
Developer License. The "clean-room" ABAP base (§6.3) is written from scratch from the documented
|
|
153
|
+
public signatures; no SAP code is copied.
|
|
154
|
+
|
|
155
|
+
---
|
|
156
|
+
|
|
157
|
+
## 5. Inputs, outputs, CLI
|
|
158
|
+
|
|
159
|
+
### Input: a folder in abapGit format
|
|
160
|
+
|
|
161
|
+
| Type | Files | MVP |
|
|
162
|
+
|---|---|---|
|
|
163
|
+
| TABL | `ztravel.tabl.xml` (DD02V, DD03P) | yes |
|
|
164
|
+
| DTEL, DOMA | `.dtel.xml`, `.doma.xml` | yes when present; otherwise, bundled catalog of the standard elements usual in RAP (`SYSUUID_X16`, `ABP_*`, `TIMESTAMPL`, `ABAP_BOOLEAN`…), type metadata only |
|
|
165
|
+
| DDLS | `.ddls.asddls` | yes |
|
|
166
|
+
| DDLX | `.ddlx.asddlxs` | yes |
|
|
167
|
+
| BDEF | `.bdef.asbdef` | yes (managed and projection) |
|
|
168
|
+
| SRVD | `.srvd.srvdsrv` | yes |
|
|
169
|
+
| SRVB | `.srvb.xml` | optional: name of the binding, version, path of the service |
|
|
170
|
+
| CLAS | `.clas.abap`, `.clas.locals_imp.abap`, `.clas.locals_def.abap` | yes (behavior pool) |
|
|
171
|
+
| MSAG | `.msag.xml` | "should": for `new_message( id = … number = … )` |
|
|
172
|
+
|
|
173
|
+
### CLI
|
|
174
|
+
|
|
175
|
+
```
|
|
176
|
+
rapt check <src> diagnostics (parsing, RAP semantics, ABAP syntax check)
|
|
177
|
+
rapt ir <src> --service ZUI_TRAVEL IR as JSON (debugging, snapshots)
|
|
178
|
+
rapt build <src> --service ZUI_TRAVEL --out <dir> [--odata-path …] [--ui5-version …]
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
The diagnostics follow the format `file:line:column: error|warning CODE message`. The exit code
|
|
182
|
+
is non-zero on error.
|
|
183
|
+
|
|
184
|
+
### Output: a standalone folder
|
|
185
|
+
|
|
186
|
+
```
|
|
187
|
+
package.json public dependencies (fastify, kysely, pg, @abaplint/runtime, jose, openid-client…)
|
|
188
|
+
vendor/ bundled runtime as a tarball: no private registry needed
|
|
189
|
+
server.mjs startup: configuration (env) → DB schema → ABAP runtime → Fastify
|
|
190
|
+
model/model.json serialized IR
|
|
191
|
+
odata/metadata.xml CSDL ($metadata)
|
|
192
|
+
db/schema.sql tables + views
|
|
193
|
+
abap/*.mjs transpiled ABP, derived types, "clean-room" base
|
|
194
|
+
webapp/ ui5.yaml OpenUI5 application, built as a standalone bundle in the Docker build
|
|
195
|
+
Dockerfile docker-compose.yml .env.example keycloak/realm.json README.md
|
|
196
|
+
```
|
|
197
|
+
|
|
198
|
+
Start: `npm start` (PostgreSQL through `DATABASE_URL`, `AUTH_MODE=none|oidc`) or
|
|
199
|
+
`docker compose up`.
|
|
200
|
+
|
|
201
|
+
---
|
|
202
|
+
|
|
203
|
+
## 6. Detailed design
|
|
204
|
+
|
|
205
|
+
### 6.1 Parsing
|
|
206
|
+
|
|
207
|
+
**BDEF (Chevrotain).** Case-insensitive lexer, `//` and `/* */` comments, namespaced names
|
|
208
|
+
(`/DMO/I_Travel`), `$self`, cardinalities `[1]`, `[0..*]`. MVP subset:
|
|
209
|
+
|
|
210
|
+
- header: `managed implementation in class … unique;`, `projection;`, `strict ( 2 );`;
|
|
211
|
+
- entity: `define [root] behavior for E [alias A]`, `persistent table T`, `lock master`,
|
|
212
|
+
`lock dependent by _A`, `etag master F`, `etag dependent by _A`, `use etag` (projection);
|
|
213
|
+
- body: `create; update; delete;`, `association _A [{ create; }]`,
|
|
214
|
+
`field ( readonly | mandatory | numbering : managed ) F1, F2;`,
|
|
215
|
+
`determination d on modify { create; update; delete; field F1, F2; }`,
|
|
216
|
+
`validation v on save { … }`, `action a [result [1] $self];`,
|
|
217
|
+
`mapping for T [corresponding] { F = col; }`;
|
|
218
|
+
- projection: `use create`, `use update`, `use delete`, `use action a`,
|
|
219
|
+
`use association _A [{ create; }]`.
|
|
220
|
+
|
|
221
|
+
The out-of-scope constructs are **recognized** by generic rules. The tool emits a clear
|
|
222
|
+
diagnostic instead of an opaque syntax error:
|
|
223
|
+
|
|
224
|
+
| Construct | Handling |
|
|
225
|
+
|---|---|
|
|
226
|
+
| `with draft`, `unmanaged`, `late numbering`, `with additional save`, `with unmanaged save`, `static` or `factory` actions, functions, `determine action`, `event` | **error**: out of the MVP scope |
|
|
227
|
+
| `authorization master`, `authorization dependent`, `side effects`, `features : instance`, `mandatory : create`, `readonly : update` | **warning**: accepted but ignored, gap described in KNOWN_GAPS.md |
|
|
228
|
+
|
|
229
|
+
**CDS (through abaplint).** The adapter extracts the name, the kind (root or child), the source
|
|
230
|
+
(table or entity), the select list with `key` and aliases, the associations and compositions
|
|
231
|
+
(cardinality, ON condition), `redirected to`, `provider contract`, and the entity and element
|
|
232
|
+
annotations converted to JSON. MVP: columns and aliases; expressions (CASE, CAST, calculations)
|
|
233
|
+
produce an "unsupported" diagnostic until an extension. Merge of the annotations: DDLX (layers
|
|
234
|
+
CUSTOMER > PARTNER > INDUSTRY > LOCALIZATION > CORE), then projection, then base view
|
|
235
|
+
(propagation), respecting `@Metadata.ignorePropagatedAnnotations`.
|
|
236
|
+
|
|
237
|
+
**SRVD.** `define service S { expose E [as Alias]; }`, with annotations.
|
|
238
|
+
|
|
239
|
+
**DDIC.** Tables read by abaplint (DD03P XML); DTEL and DOMA by abaplint; bundled catalog for
|
|
240
|
+
the standard elements. The client column (`CLNT`) is ignored (decision 9).
|
|
241
|
+
|
|
242
|
+
### 6.2 IR
|
|
243
|
+
|
|
244
|
+
Sketch of the types of the `model` package:
|
|
245
|
+
|
|
246
|
+
```ts
|
|
247
|
+
interface Model { version: 1; service: Service; bos: BusinessObject[]; projections: ProjectionBO[]; tables: Table[] }
|
|
248
|
+
|
|
249
|
+
type AbapType = { kind: "char" | "numc" | "string" | "dec" | "curr" | "quan" | "int1" | "int2" | "int4"
|
|
250
|
+
| "int8" | "fltp" | "dats" | "tims" | "timestamp" | "timestampl" | "utclong"
|
|
251
|
+
| "raw" | "rawstring" | "cuky" | "unit" | "bool";
|
|
252
|
+
length?: number; decimals?: number; ddic?: string };
|
|
253
|
+
|
|
254
|
+
interface Field { name: string; type: AbapType; column?: string; key: boolean;
|
|
255
|
+
semantics?: "createdBy" | "createdAt" | "lastChangedBy" | "lastChangedAt" | "localInstanceLastChangedAt";
|
|
256
|
+
currencyRef?: string; annotations: Annotations; src: SourceRef }
|
|
257
|
+
|
|
258
|
+
interface Entity { name: string; alias: string; root: boolean; table?: string; baseEntity?: string;
|
|
259
|
+
fields: Field[]; associations: Association[]; annotations: Annotations }
|
|
260
|
+
|
|
261
|
+
interface Association { name: string; target: string; kind: "composition" | "toParent" | "association";
|
|
262
|
+
cardinality: [min: number, max: number | "*"]; on: Array<[source: string, target: string]> }
|
|
263
|
+
|
|
264
|
+
interface Trigger { create?: true; update?: true; delete?: true; fields: string[] }
|
|
265
|
+
interface HandlerRef { abapClass: string; method: string; params: Record<string, string> }
|
|
266
|
+
|
|
267
|
+
interface EntityBehavior {
|
|
268
|
+
entity: string; persistentTable: string;
|
|
269
|
+
lock: { master: true } | { dependentBy: string };
|
|
270
|
+
etag?: { master: string } | { dependentBy: string };
|
|
271
|
+
operations: { create: boolean; update: boolean; delete: boolean; createBy: string[] };
|
|
272
|
+
fieldControl: Record<string, Array<"readonly" | "mandatory">>; numberingManaged: string[];
|
|
273
|
+
determinations: Array<{ name: string; on: "modify" | "save"; triggers: Trigger; handler: HandlerRef }>;
|
|
274
|
+
validations: Array<{ name: string; triggers: Trigger; handler: HandlerRef }>;
|
|
275
|
+
actions: Array<{ name: string; result?: { cardinality: "1"; self: true }; handler: HandlerRef }>;
|
|
276
|
+
mapping: Record<string, string>; src: SourceRef }
|
|
277
|
+
```
|
|
278
|
+
|
|
279
|
+
Semantic validations (positioned diagnostics): known entity, field and association; BDEF mapping
|
|
280
|
+
consistent with the CDS view and the table; `etag master` field existing and of a timestamp type;
|
|
281
|
+
`numbering : managed` field a key of type RAW 16; fields of the triggers existing; the projection
|
|
282
|
+
only uses capabilities of the base; each validation, determination and action has a handler
|
|
283
|
+
method in the ABP.
|
|
284
|
+
|
|
285
|
+
### 6.3 ABP → JavaScript
|
|
286
|
+
|
|
287
|
+
1. abaplint **syntax check** of the original sources (ABAP for Cloud Development). The errors
|
|
288
|
+
point to the original lines.
|
|
289
|
+
2. **Derived types.** For each BDEF, a generated ABAP interface (`ZRAP_DT_<BDEF>`, or a hashed
|
|
290
|
+
name beyond 30 characters). Per entity: groups `%key`, `%pky`, `%tky`, `%data`, `%control`;
|
|
291
|
+
tables `FOR CREATE`, `UPDATE`, `DELETE`, `READ IMPORT`, `READ RESULT`, `ACTION IMPORT`,
|
|
292
|
+
`ACTION RESULT`; structures `RESPONSE FOR FAILED/REPORTED/MAPPED` (`%cid`, `%cid_ref`,
|
|
293
|
+
`%fail`, `%msg`, `%element`, `%op`…). All according to the ABAP documentation of the BDEF
|
|
294
|
+
derived types.
|
|
295
|
+
3. **Rewrite.** abaplint provides the AST and the positions; targeted text edits are applied,
|
|
296
|
+
preserving the line numbers:
|
|
297
|
+
|
|
298
|
+
| In the ABP | Becomes |
|
|
299
|
+
|---|---|
|
|
300
|
+
| `METHODS v FOR VALIDATE ON SAVE IMPORTING keys FOR Travel~v.` | `METHODS v IMPORTING keys TYPE <dt>=>tt_travel_keys CHANGING failed TYPE <dt>=>ts_failed reported TYPE <dt>=>ts_reported.` |
|
|
301
|
+
| `FOR DETERMINE ON MODIFY …`, `FOR MODIFY IMPORTING keys FOR ACTION Travel~a RESULT result` | same principle; `result` and `mapped` typed according to the operation |
|
|
302
|
+
| `TYPE TABLE FOR UPDATE zr_travel\\travel`, `TYPE STRUCTURE FOR …`, `TYPE RESPONSE FOR …` | `TYPE <dt>=>tt_travel_update`, etc. |
|
|
303
|
+
| `RESULT DATA(x)`, `FAILED DATA(f)`, `REPORTED DATA(r)`, `MAPPED DATA(m)` of an EML statement | `DATA x TYPE …` hoisted on the same line, then `RESULT x` |
|
|
304
|
+
| EML operand `CORRESPONDING #( … )` or `VALUE #( … )` | `#` replaced by the expected derived type |
|
|
305
|
+
| handlers the tool does not call (for example `FOR GLOBAL AUTHORIZATION`) | signature typed the same way, so that the class transpiles; method never called |
|
|
306
|
+
| everything else, EML statements included | unchanged: the transpiler lowers the EML to `KERNEL_RAP_EML` |
|
|
307
|
+
|
|
308
|
+
4. **Registry of the transpiler**: rewritten ABP, type interface, shadow DDLS (V8), tables, and
|
|
309
|
+
a "clean-room" base written by us: `CL_ABAP_BEHAVIOR_HANDLER` (`new_message_with_text`,
|
|
310
|
+
`new_message`), `IF_ABAP_BEHV` (`mk`, `cause`…), `IF_ABAP_BEHV_MESSAGE`.
|
|
311
|
+
5. **Transpilation** (`unknownTypes: compileError`) → `.mjs` modules. The tool writes the
|
|
312
|
+
**registry of the handlers** into `model.json`: BDEF validation, determination or action →
|
|
313
|
+
transpiled local class, method, names of the parameters.
|
|
314
|
+
|
|
315
|
+
Safeguard: the transpiler and the runtime are never patched, they are given standard ABAP. An
|
|
316
|
+
unhandled construct produces a diagnostic and an entry in KNOWN_GAPS.md.
|
|
317
|
+
|
|
318
|
+
**Execution.** The runtime instantiates the local class (for example
|
|
319
|
+
`abap.Classes['CLAS-ZBP_R_TRAVEL-LHC_TRAVEL']`). It builds `keys`, `failed`, `reported` and
|
|
320
|
+
`result` from the generated types, calls the method, then reads the responses back. The
|
|
321
|
+
`KERNEL_RAP_EML` kernel links each EML call to the buffer of the current LUW, found through
|
|
322
|
+
`AsyncLocalStorage`. The abaplint runtime has a **global state** (`sy-*`, static attributes)
|
|
323
|
+
shared between requests: the ABP calls are therefore **serialized** in a process. Scaling is done
|
|
324
|
+
with processes or containers (decision 11).
|
|
325
|
+
|
|
326
|
+
### 6.4 SQL (PostgreSQL)
|
|
327
|
+
|
|
328
|
+
The tables come from the TABLs: lower-case names, client column removed, primary key, child →
|
|
329
|
+
parent foreign key for the compositions, index on the parent key. A SQL view is generated for
|
|
330
|
+
each CDS view: `zr_*` on the tables, then `zc_*` on `zr_*`. The OData reads go through the
|
|
331
|
+
`zc_*` views; the writes go through the RAP runtime, to the tables. The DDL is idempotent
|
|
332
|
+
(`IF NOT EXISTS`). Schema evolution (ALTER) is not in the MVP.
|
|
333
|
+
|
|
334
|
+
| ABAP | PostgreSQL | Edm |
|
|
335
|
+
|---|---|---|
|
|
336
|
+
| CHAR(n) | `varchar(n)`, trailing blanks removed | String, MaxLength=n |
|
|
337
|
+
| NUMC(n) | `varchar(n)`, digits with leading zeros | String + `Common.IsDigitSequence` |
|
|
338
|
+
| STRING, SSTRING(n) | `text`, `varchar(n)` | String |
|
|
339
|
+
| INT1, INT2, INT4, INT8 | `smallint`, `smallint`, `integer`, `bigint` | Byte, Int16, Int32, Int64 |
|
|
340
|
+
| DEC, CURR, QUAN (p,s) | `numeric(p,s)` | Decimal, Precision and Scale |
|
|
341
|
+
| FLTP | `double precision` | Double |
|
|
342
|
+
| DATS | `date`; initial value `00000000` ↔ NULL | Date |
|
|
343
|
+
| TIMS | `time(0)` | TimeOfDay |
|
|
344
|
+
| TIMESTAMP (DEC 15) | `timestamptz(0)` | DateTimeOffset, Precision 0 |
|
|
345
|
+
| TIMESTAMPL (DEC 21,7), UTCLONG | `timestamptz(6)` | DateTimeOffset, Precision 7 |
|
|
346
|
+
| RAW(16) (`SYSUUID_X16`) | `uuid` | Guid |
|
|
347
|
+
| other RAW(n), RAWSTRING | `bytea` | Binary |
|
|
348
|
+
| CHAR(1) `ABAP_BOOLEAN` | `boolean` | Boolean |
|
|
349
|
+
| CUKY, UNIT | `varchar(5)`, `varchar(3)` | String; the amount carries `Measures.ISOCurrency` or `Measures.Unit` |
|
|
350
|
+
|
|
351
|
+
### 6.5 $metadata (CSDL XML)
|
|
352
|
+
|
|
353
|
+
Conventions taken from RAP, so that an existing Fiori Elements application finds what it
|
|
354
|
+
expects: schema `com.sap.gateway.srvd.<srvd>.v0001` (alias `SAP__self`), types `<Alias>Type`,
|
|
355
|
+
entity sets `<Alias>`, navigations with `Partner` and `NavigationPropertyBinding`, bound actions
|
|
356
|
+
with an `_it` parameter. The vocabularies (Core, Capabilities, Measures on the OASIS side;
|
|
357
|
+
Common, UI on the SAP side) are referenced by their public URL.
|
|
358
|
+
|
|
359
|
+
| Source | CSDL annotation |
|
|
360
|
+
|---|---|
|
|
361
|
+
| `@EndUserText.label` | `Common.Label` |
|
|
362
|
+
| `@UI.lineItem` (including `#FOR_ACTION`) | `UI.LineItem` (`UI.DataField`, `UI.DataFieldForAction`, `UI.Importance`) |
|
|
363
|
+
| `@UI.selectionField` | `UI.SelectionFields` |
|
|
364
|
+
| `@UI.headerInfo` | `UI.HeaderInfo` |
|
|
365
|
+
| `@UI.facet` | `UI.Facets`: `ReferenceFacet` to Identification, FieldGroup or the LineItem of an association; `CollectionFacet` |
|
|
366
|
+
| `@UI.identification`, `@UI.fieldGroup` | `UI.Identification`, `UI.FieldGroup#Qualifier` |
|
|
367
|
+
| `@UI.hidden` | `UI.Hidden` |
|
|
368
|
+
| `@ObjectModel.text.element` | `Common.Text` |
|
|
369
|
+
| `@Semantics.amount.currencyCode`, `@Semantics.quantity.unitOfMeasure` | `Measures.ISOCurrency`, `Measures.Unit` |
|
|
370
|
+
| `field ( readonly )`, `numbering : managed` | `Core.Computed` |
|
|
371
|
+
| `field ( mandatory )` | `Common.FieldControl` Mandatory |
|
|
372
|
+
| `etag master` | `Core.OptimisticConcurrency` on the entity set |
|
|
373
|
+
| available operations (create, update, delete, create by association) | `Capabilities.InsertRestrictions`, `UpdateRestrictions`, `DeleteRestrictions`, `NavigationRestrictions` |
|
|
374
|
+
|
|
375
|
+
### 6.6 OData V4 runtime
|
|
376
|
+
|
|
377
|
+
Goal: that an existing Fiori Elements V4 application (outside this project, used under the SAP
|
|
378
|
+
license of its owner) can consume the service. Our tests only use OpenUI5.
|
|
379
|
+
|
|
380
|
+
The table gives the requested scope, plus the additions I propose because a V4 List Report /
|
|
381
|
+
Object Page needs them (decision 5):
|
|
382
|
+
|
|
383
|
+
| Function | Requested | Proposed addition |
|
|
384
|
+
|---|---|---|
|
|
385
|
+
| Service document, `$metadata` | ✓ | |
|
|
386
|
+
| GET collection; GET entity, short key `Travel(<guid>)` and named key `Travel(TravelUUID=<guid>)` | ✓ | |
|
|
387
|
+
| GET through a navigation `Travel(…)/_Booking` (table of the Object Page) | | ✓ |
|
|
388
|
+
| `$filter`: eq, ne, gt, lt, and, or, contains | ✓ | ge, le, not, startswith, endswith, parentheses, `null` (the FE filter bar produces them) |
|
|
389
|
+
| `$orderby`, `$top`, `$skip`, `$count=true` | ✓ | `/$count` segment |
|
|
390
|
+
| One-level `$expand`, with nested `$select`, `$orderby`, `$top`, `$skip` | ✓ | |
|
|
391
|
+
| `$select` | ✓ | |
|
|
392
|
+
| Simple `$search` (ILIKE on the `@Search.defaultSearchElement` fields) | | ✓ |
|
|
393
|
+
| POST, PATCH, DELETE | ✓ | POST through a navigation `Travel(…)/_Booking`; one-level deep insert |
|
|
394
|
+
| Bound actions | ✓ | |
|
|
395
|
+
| Multipart `$batch`, atomic changesets, `Content-ID` | ✓ | `$<id>` references inside a changeset |
|
|
396
|
+
| ETag: `@odata.etag`, `If-Match`, 412 on a mismatch | ✓ | 428 when `If-Match` is missing |
|
|
397
|
+
| `IEEE754Compatible=true` (Decimal and Int64 as strings) | | ✓ (UI5 always sends it) |
|
|
398
|
+
| CSRF token (`X-CSRF-Token: Fetch` on HEAD or GET) | | ✓ |
|
|
399
|
+
| Messages: V4 error with `target` and `@Common.numericSeverity`; success and warnings in the `sap-messages` header | | ✓ |
|
|
400
|
+
| `sap-*` parameters (sap-client, sap-language…) | | accepted and ignored |
|
|
401
|
+
|
|
402
|
+
Implementation: a Fastify plugin; the system options are parsed by Chevrotain, then the AST is
|
|
403
|
+
translated into parameterized Kysely queries (never SQL concatenation), with an allow list of
|
|
404
|
+
properties from the IR. A changeset corresponds to a RAP LUW and to a PostgreSQL transaction.
|
|
405
|
+
The JSON `$batch` format (OData 4.01) is not in the MVP.
|
|
406
|
+
|
|
407
|
+
Path of the service: by default the one of a RAP binding,
|
|
408
|
+
`/sap/opu/odata4/sap/<binding>/srvd/sap/<srvd>/0001/`, so that an existing FE application points
|
|
409
|
+
to it without changing its manifest. The path is configurable (decision 6).
|
|
410
|
+
|
|
411
|
+
### 6.7 RAP managed runtime (without draft)
|
|
412
|
+
|
|
413
|
+
- **Transactional buffer** per LUW: instances per entity (state loaded, created, updated or
|
|
414
|
+
deleted), changed fields (to evaluate the triggers), mapping `%cid` ↔ keys.
|
|
415
|
+
- **Operations**:
|
|
416
|
+
- create, with `managed` numbering (UUID v4);
|
|
417
|
+
- update driven by `%control` (a PATCH sets the fields it sends);
|
|
418
|
+
- delete, cascading on the compositions;
|
|
419
|
+
- create by association, with propagation of the parent key;
|
|
420
|
+
- instance actions (`result [1] $self`).
|
|
421
|
+
- **Fields**:
|
|
422
|
+
- `readonly`: ignored or refused for an external consumer (OData), allowed in local mode (ABP);
|
|
423
|
+
- `mandatory`: no runtime check, as in RAP (V12);
|
|
424
|
+
- administrative fields filled according to `@Semantics.user.createdBy`, `lastChangedBy`,
|
|
425
|
+
`@Semantics.systemDateTime.createdAt`, `lastChangedAt` and `localInstanceLastChangedAt`.
|
|
426
|
+
- **On modify determinations**: run at the end of each modifying request, for the instances
|
|
427
|
+
whose trigger (`create`, `update`, `delete`, `field …`) was touched. They can trigger others;
|
|
428
|
+
the loop is bounded.
|
|
429
|
+
- **Save sequence**:
|
|
430
|
+
1. finalize (on save determinations);
|
|
431
|
+
2. check_before_save (validations whose triggers were touched in the LUW);
|
|
432
|
+
3. if `failed` is not empty: rollback, then OData error with the messages of `reported`;
|
|
433
|
+
4. otherwise save (INSERT, UPDATE, DELETE), COMMIT, cleanup.
|
|
434
|
+
- **ETag**: check of `If-Match` on the `etag master` field before update, delete and action.
|
|
435
|
+
- **Lock**: `lock master` → `SELECT … FOR UPDATE NOWAIT` on the root row, in the transaction of
|
|
436
|
+
the changeset; `lock dependent` → lock of the root. The HTTP code returned when the instance is
|
|
437
|
+
locked will be aligned with SAP (to check).
|
|
438
|
+
- **Identity**: the authenticated user feeds `sy-uname` and the `createdBy`/`lastChangedBy`
|
|
439
|
+
fields.
|
|
440
|
+
|
|
441
|
+
### 6.8 Authentication
|
|
442
|
+
|
|
443
|
+
- `AUTH_MODE=none` (dev): fixed, configurable user.
|
|
444
|
+
- `AUTH_MODE=oidc`, generic (discovery through `/.well-known/openid-configuration`):
|
|
445
|
+
- API: Bearer JWT token verified through JWKS with `jose` (`iss`, `aud`, `exp`, optional
|
|
446
|
+
required role);
|
|
447
|
+
- browser: BFF pattern, Authorization Code + PKCE with `openid-client`, session in an
|
|
448
|
+
encrypted cookie (`HttpOnly`, `SameSite=Lax`), CSRF token required on the modifying requests.
|
|
449
|
+
- The claim that feeds `sy-uname` is configurable (`preferred_username` by default). The CHAR(12)
|
|
450
|
+
user columns are truncated, with a warning.
|
|
451
|
+
- Keycloak: docker-compose profile with a realm import (client, test users). In Vitest: fake OIDC
|
|
452
|
+
provider (`oauth2-mock-server`).
|
|
453
|
+
|
|
454
|
+
### 6.9 Generated OpenUI5 UI (freestyle, without sap.fe)
|
|
455
|
+
|
|
456
|
+
- `sap.ui.model.odata.v4.ODataModel` of OpenUI5, with `autoExpandSelect`. It is the model of
|
|
457
|
+
Fiori Elements V4: the `$batch` requests have the same shape.
|
|
458
|
+
- **List Report**:
|
|
459
|
+
- `sap.f.DynamicPage`;
|
|
460
|
+
- filter bar generated from `UI.selectionField`, the control depending on the Edm type;
|
|
461
|
+
search field if `@Search.searchable`;
|
|
462
|
+
- `sap.m.Table` from `UI.lineItem` (sorting, growing, `$count`);
|
|
463
|
+
- Create and Delete buttons according to the capabilities; action buttons from `#FOR_ACTION`.
|
|
464
|
+
- **Object Page**:
|
|
465
|
+
- `sap.uxap.ObjectPageLayout`, header from `UI.headerInfo`;
|
|
466
|
+
- sections from `UI.facet`: forms for Identification and FieldGroup, table for the
|
|
467
|
+
composition;
|
|
468
|
+
- display and edit modes, Save / Cancel, actions;
|
|
469
|
+
- `mandatory` → required field, checked on the client before the save; `readonly` → not
|
|
470
|
+
editable;
|
|
471
|
+
- messages in a `MessagePopover`.
|
|
472
|
+
- The child object page (Booking) is generated the same way; the i18n labels come from
|
|
473
|
+
`@EndUserText`.
|
|
474
|
+
- `sap.ui.mdc` (present in OpenUI5) is a possible evolution, not in the MVP. OpenUI5 version
|
|
475
|
+
pinned on an LTS, 1.136 by default.
|
|
476
|
+
|
|
477
|
+
### 6.10 Packaging
|
|
478
|
+
|
|
479
|
+
- Multi-stage **Dockerfile**: UI build (`ui5 build self-contained`), installation of the
|
|
480
|
+
production dependencies, `node:22-alpine` image, non-root user, `HEALTHCHECK`.
|
|
481
|
+
- **docker-compose**: `app` + `postgres:16-alpine` (volume, healthcheck); `auth` profile with
|
|
482
|
+
Keycloak.
|
|
483
|
+
- On startup, the application applies `db/schema.sql` (idempotent) and keeps a schema version
|
|
484
|
+
table.
|
|
485
|
+
|
|
486
|
+
---
|
|
487
|
+
|
|
488
|
+
## 7. Test strategy
|
|
489
|
+
|
|
490
|
+
| Level | Content | Tools |
|
|
491
|
+
|---|---|---|
|
|
492
|
+
| Unit | Grammars (positive and negative cases, error positions), IR construction, generators (assertions + snapshots), OData parsers, ABAP ↔ SQL ↔ Edm type conversions | Vitest |
|
|
493
|
+
| Oracle | BDEF compared with the abaplint parser; CDS and BDEF of the SAP-samples corpus, read-only | Vitest |
|
|
494
|
+
| ABP | Snapshots of the rewrite; transpilation then execution of the handlers on a fake kernel; tests of the `KERNEL_RAP_EML` contract and of the access to the private methods | Vitest + abaplint |
|
|
495
|
+
| Integration | RAP runtime + SQL on a real in-memory PostgreSQL engine | PGlite |
|
|
496
|
+
| OData conformance | Requests as Fiori sends them (appendix A), including raw `$batch` bodies. First written from the URLs of a V4 List Report, then completed with real traces: the OpenUI5 ODataModel V4 is driven with Playwright and the captured requests are frozen in reference files. | `fastify.inject` + PGlite |
|
|
497
|
+
| E2E | `rapt build` on the fixture → `docker compose up` → HTTP scenarios and UI journeys (filter, open, edit, action, create, delete). Variant without Docker on a local PostgreSQL. | Playwright |
|
|
498
|
+
|
|
499
|
+
CI: GitHub Actions (typecheck and tests on each push, Docker e2e job).
|
|
500
|
+
|
|
501
|
+
---
|
|
502
|
+
|
|
503
|
+
## 8. Milestones
|
|
504
|
+
|
|
505
|
+
Each milestone ends with green Vitest tests, a commit pushed to the branch and a short report.
|
|
506
|
+
|
|
507
|
+
### M1 — Foundation, fixture, BDEF parser, IR
|
|
508
|
+
|
|
509
|
+
- **Deliverables**
|
|
510
|
+
- Monorepo foundation (pnpm, strict TypeScript, ESM, Vitest, CI), LICENSES.md, KNOWN_GAPS.md,
|
|
511
|
+
README.
|
|
512
|
+
- Complete `fixtures/travel/` fixture in abapGit format (§10), ABP included (used from M4).
|
|
513
|
+
- Chevrotain BDEF (managed and projection) and SRVD grammars; abaplint CDS, DDLX and DDIC
|
|
514
|
+
adapters; catalog of the standard data elements.
|
|
515
|
+
- `model` package: types of the IR, construction, semantic validations, positioned
|
|
516
|
+
diagnostics.
|
|
517
|
+
- `rapt check` and `rapt ir` CLI.
|
|
518
|
+
- **Tests**: grammar (positive, negative, error positions); abaplint oracle (same entities,
|
|
519
|
+
aliases, operations); snapshot of the IR of the fixture; diagnostics on deliberately wrong
|
|
520
|
+
sources.
|
|
521
|
+
- **Exit**: `rapt check fixtures/travel` without errors; `rapt ir` produces a complete and stable
|
|
522
|
+
IR.
|
|
523
|
+
|
|
524
|
+
### M2 — SQL and $metadata generators
|
|
525
|
+
|
|
526
|
+
- **Deliverables**: DDL (tables, constraints, R and C views); CSDL with annotations; `rapt build`
|
|
527
|
+
writes `db/schema.sql` and `odata/metadata.xml`.
|
|
528
|
+
- **Tests**: DDL run on PGlite, then insertion and read through the views; well-formed CSDL,
|
|
529
|
+
XPath assertions, snapshot; IR ↔ CSDL cross-check.
|
|
530
|
+
- **Exit**: green tests.
|
|
531
|
+
|
|
532
|
+
### M3 — OData read runtime (and authentication foundation)
|
|
533
|
+
|
|
534
|
+
- **Deliverables**: Fastify plugin; parsers of the URLs and options (Chevrotain); V4 JSON
|
|
535
|
+
serialization; read `$batch`; CSRF; errors; authentication in dev mode and Bearer.
|
|
536
|
+
- **Tests**: parsers; "read" conformance suite (appendix A, requests 1 to 11); security
|
|
537
|
+
(injection attempts, unknown properties).
|
|
538
|
+
- **Exit**: the read requests of a List Report and of an Object Page pass.
|
|
539
|
+
|
|
540
|
+
### M4 — Writes, EML, transpiled ABP
|
|
541
|
+
|
|
542
|
+
- **Deliverables**: RAP managed runtime (§6.7), ABP chain (§6.3), EML kernel, OData writes and
|
|
543
|
+
changesets, messages.
|
|
544
|
+
- **Tests**
|
|
545
|
+
- Unit: buffer, triggers, save sequence, snapshots of the rewrite, contract of the kernel.
|
|
546
|
+
- Integration through OData on PGlite:
|
|
547
|
+
- creation of a Travel → `OverallStatus = 'O'` (determination);
|
|
548
|
+
- inconsistent dates → 400, message targeted on `EndDate`, rollback;
|
|
549
|
+
- stale ETag → 412;
|
|
550
|
+
- `acceptTravel` → `'A'`;
|
|
551
|
+
- creation of a Booking through `_Booking`;
|
|
552
|
+
- cascading delete;
|
|
553
|
+
- atomic changeset.
|
|
554
|
+
- **Exit**: green tests; KNOWN_GAPS.md up to date.
|
|
555
|
+
|
|
556
|
+
### M5 — Generated UI
|
|
557
|
+
|
|
558
|
+
- **Deliverables**: OpenUI5 generator (§6.9).
|
|
559
|
+
- **Tests**: snapshots of the views and of the manifest; Playwright journey on the application
|
|
560
|
+
(PGlite or local PostgreSQL): list, filter, sort, open, edit, save, run the action, create with
|
|
561
|
+
children, delete, see a validation message.
|
|
562
|
+
- **Exit**: green journey.
|
|
563
|
+
|
|
564
|
+
### M6 — Docker packaging, complete OIDC, e2e
|
|
565
|
+
|
|
566
|
+
- **Deliverables**: package.json, Dockerfile, docker-compose (app + Postgres, Keycloak profile);
|
|
567
|
+
bundled runtime; OIDC BFF; README of the generated application.
|
|
568
|
+
- **Tests**: e2e — build of the fixture, `docker compose up`, HTTP and Playwright suite, with and
|
|
569
|
+
without Keycloak; variant without Docker.
|
|
570
|
+
- **Exit**: green tests locally and in CI.
|
|
571
|
+
|
|
572
|
+
---
|
|
573
|
+
|
|
574
|
+
## 9. Risks
|
|
575
|
+
|
|
576
|
+
| Risk | Probability | Impact | Mitigation |
|
|
577
|
+
|---|---|---|---|
|
|
578
|
+
| RAP derived types not resolved by abaplint (V4) | observed | blocking for the ABP | Generated types + rewrite (PoC V10); eventually, an upstream contribution. |
|
|
579
|
+
| EML forms not lowered (V6), especially `READ … BY \_Booking`, common in real determinations | high | real ABPs refused | Diagnostic at compile time, KNOWN_GAPS entry; upstream contribution if you accept it. |
|
|
580
|
+
| Very active abaplint libraries (daily releases). The `KERNEL_RAP_EML` contract is recent; the access to the private methods goes through an internal detail of the generated code (`FRIENDS_ACCESS_INSTANCE`). | medium | breakage on upgrade | Exact pinned versions, dedicated contract tests, explicit upgrades. |
|
|
581
|
+
| Global state of the ABAP runtime (`sy-*`, static attributes) shared between requests | certain | data crossed between requests | Serialized ABP calls; scaling with processes. |
|
|
582
|
+
| RAP semantic fidelity (order of the determinations, triggers, HTTP codes, format of the messages) | medium | behavior different from SAP | Behavior tests written from the ABAP documentation; gaps listed in KNOWN_GAPS. |
|
|
583
|
+
| OData V4 conformance with UI5 and FE (IEEE754Compatible, `$batch`, ETag, messages) | medium | UI that does not work | Conformance suite on real requests + e2e with the real OpenUI5 ODataModel V4. |
|
|
584
|
+
| CDS coverage beyond the MVP (CASE, CAST, joins, parameters, unions) | low for the MVP | parser or SQL to extend | Isolated adapter; complementary parser if needed. |
|
|
585
|
+
| Precision of the timestamps (7 decimals in ABAP, 6 in PostgreSQL) | certain | low | Timestamps generated to the microsecond; documented. |
|
|
586
|
+
| Security: injection through `$filter`, CSRF, XSS in the UI | low | high | Parameterized queries, allow list of properties, CSRF token, CSP headers, dedicated tests. |
|
|
587
|
+
| Legal: "clean-room" base (`CL_ABAP_BEHAVIOR_HANDLER`…) | low | medium | Written from scratch from the public signatures; no SAP code copied; no SAP dependency. |
|
|
588
|
+
| Duration and size of the OpenUI5 build | medium | low | Build in a Docker stage, with a cache; OpenUI5 CDN in dev mode. |
|
|
589
|
+
| Docker unavailable in some environments (like this one) | medium | Docker e2e cannot run | Equivalent e2e on a local PostgreSQL; Docker job in CI. |
|
|
590
|
+
|
|
591
|
+
Gaps already identified, which will go to KNOWN_GAPS.md: EML forms of V6; `IN LOCAL MODE` and
|
|
592
|
+
`PRIVILEGED` ignored (V7); authorizations, dynamic feature control and side effects ignored with
|
|
593
|
+
a warning; conversion exits (ALPHA…) and ISO conversion of the languages not handled; precision
|
|
594
|
+
of the timestamps; client ignored; serialized ABP execution.
|
|
595
|
+
|
|
596
|
+
---
|
|
597
|
+
|
|
598
|
+
## 10. Travel / Booking fixture
|
|
599
|
+
|
|
600
|
+
The fixture is delivered in [fixtures/travel](fixtures/travel) (abapGit format); its detailed
|
|
601
|
+
description is in [fixtures/travel/README.md](fixtures/travel/README.md). The objects carry the
|
|
602
|
+
`ZRT_` prefix to avoid collisions with the objects of the RAP tutorials when it is imported into
|
|
603
|
+
a real system.
|
|
604
|
+
|
|
605
|
+
| Object | Type | Role |
|
|
606
|
+
|---|---|---|
|
|
607
|
+
| `ZRT_TRAVEL`, `ZRT_BOOKING` | TABL | Persistent tables |
|
|
608
|
+
| `ZRT_R_TRAVEL` | DDLS | Root view entity, `composition [0..*] of ZRT_R_Booking` |
|
|
609
|
+
| `ZRT_R_BOOKING` | DDLS | Child view entity, `association to parent` |
|
|
610
|
+
| `ZRT_C_TRAVEL`, `ZRT_C_BOOKING` | DDLS, DDLX | Projections and UI annotations |
|
|
611
|
+
| `ZRT_R_TRAVEL`, `ZRT_C_TRAVEL` | BDEF | Managed strict(2) behavior and projection |
|
|
612
|
+
| `ZBP_RT_R_TRAVEL` | CLAS | Behavior pool |
|
|
613
|
+
| `ZRT_UI_TRAVEL`, `ZRT_UI_TRAVEL_O4` | SRVD, SRVB | Service and OData V4 binding |
|
|
614
|
+
|
|
615
|
+
Compared with the initial sketch, the fixture covers more non-draft features (decision D14):
|
|
616
|
+
global authorization, instance feature control on the `acceptTravel` action, internal action
|
|
617
|
+
`reCalcTotalPrice` called by two `calculateTotalPrice` determinations (on Travel and on Booking),
|
|
618
|
+
with a `READ … BY \_Booking` and a `READ … BY \_Travel` read.
|
|
619
|
+
|
|
620
|
+
---
|
|
621
|
+
|
|
622
|
+
## 11. Decisions taken
|
|
623
|
+
|
|
624
|
+
The owner of the project delegated the technical choices, with one requirement: all the features
|
|
625
|
+
of a non-draft RAP BO must be respected. The recommendations were all retained, with three
|
|
626
|
+
adjustments due to this requirement. The details are in [docs/DECISIONS.md](docs/DECISIONS.md).
|
|
627
|
+
|
|
628
|
+
| # | Decision | Adjustment |
|
|
629
|
+
|---|---|---|
|
|
630
|
+
| 1 | Chevrotain BDEF grammar, abaplint parser as an oracle (D1) | |
|
|
631
|
+
| 2 | Generated derived types + rewrite of the ABP (D2) | The pass also lowers `READ … BY \_assoc`, `LINK` and `CREATE BY \_assoc`, which the transpiler does not handle: they are common features of a non-draft BO. |
|
|
632
|
+
| 3 | Fastify (D3) | |
|
|
633
|
+
| 4 | Reads through generated SQL views (D4) | |
|
|
634
|
+
| 5 | Additions to the OData scope (D5) | |
|
|
635
|
+
| 6 | Path of the service modeled on RAP, configurable (D6) | |
|
|
636
|
+
| 7 | Fixture activatable on a real system (D7) | The global authorization of the fixture is really run, not only accepted. |
|
|
637
|
+
| 8 | Timestamps as `timestamptz(6)` (D8) | |
|
|
638
|
+
| 9 | Client removed (D9) | |
|
|
639
|
+
| 10 | OpenUI5 1.136 LTS, `sap.m`, `sap.f`, `sap.uxap` (D10) | |
|
|
640
|
+
| 11 | Serialized ABP execution (D11) | The lock is reentrant for the LUW that holds it. |
|
|
641
|
+
| 12 | `@open-rap/*`, `rapt` CLI, MIT license (D12) | |
|
|
642
|
+
| 13 | Extract of the SAP-samples corpus to test the parsers (D13) | |
|
|
643
|
+
|
|
644
|
+
Complementary decisions (D14 to D21): complete non-draft scope, runtime driven by the IR,
|
|
645
|
+
canonical representation of the values, toolchain, PGlite for the tests, "shadow" DDIC for the
|
|
646
|
+
transpiler, semantics of `mandatory`, triggering of the determinations.
|
|
647
|
+
|
|
648
|
+
---
|
|
649
|
+
|
|
650
|
+
## Appendix A — requests of the conformance suite
|
|
651
|
+
|
|
652
|
+
Shapes sent by the UI5 ODataModel V4 and by a Fiori Elements V4 List Report / Object Page. Paths
|
|
653
|
+
relative to the root of the service, except the first line.
|
|
654
|
+
|
|
655
|
+
```
|
|
656
|
+
HEAD /sap/opu/odata4/sap/zrt_ui_travel_o4/srvd/sap/zrt_ui_travel/0001/ X-CSRF-Token: Fetch
|
|
657
|
+
GET $metadata Accept: application/xml
|
|
658
|
+
POST $batch Content-Type: multipart/mixed;boundary=batch_id-1731160000000-12
|
|
659
|
+
|
|
660
|
+
--batch_id-1731160000000-12
|
|
661
|
+
Content-Type:application/http
|
|
662
|
+
Content-Transfer-Encoding:binary
|
|
663
|
+
|
|
664
|
+
GET Travel?$count=true&$select=AgencyID,BeginDate,CurrencyCode,EndDate,OverallStatus,TotalPrice,TravelID,TravelUUID&$skip=0&$top=30 HTTP/1.1
|
|
665
|
+
Accept:application/json;odata.metadata=minimal;IEEE754Compatible=true
|
|
666
|
+
Accept-Language:en
|
|
667
|
+
|
|
668
|
+
|
|
669
|
+
--batch_id-1731160000000-12--
|
|
670
|
+
```
|
|
671
|
+
|
|
672
|
+
| # | Case | Request (in the `$batch`) |
|
|
673
|
+
|---|---|---|
|
|
674
|
+
| 1 | Initial list | `GET Travel?$count=true&$select=…&$skip=0&$top=30` |
|
|
675
|
+
| 2 | Filter bar | `GET Travel?$count=true&$filter=OverallStatus eq 'O' and (AgencyID eq '070001' or AgencyID eq '070002')&$select=…&$skip=0&$top=30` |
|
|
676
|
+
| 3 | Date range | `$filter=BeginDate ge 2026-01-01 and BeginDate le 2026-12-31` |
|
|
677
|
+
| 4 | Contains, starts with | `$filter=contains(Description,'Paris')`, `$filter=startswith(TravelID,'000')` |
|
|
678
|
+
| 5 | Exclusion | `$filter=not (OverallStatus eq 'X')`, `$filter=OverallStatus ne 'X'` |
|
|
679
|
+
| 6 | Sorting | `$orderby=BeginDate desc,TravelID` |
|
|
680
|
+
| 7 | Search | `$search=Paris` |
|
|
681
|
+
| 8 | Scrolling | `$skip=30&$top=30` |
|
|
682
|
+
| 9 | Object Page | `GET Travel(3f2504e0-4f89-11d3-9a0c-0305e82c3301)?$select=…` |
|
|
683
|
+
| 10 | Table of the Object Page | `GET Travel(3f2504e0-…)/_Booking?$count=true&$orderby=BookingID&$select=BookingID,BookingUUID,CurrencyCode,FlightDate,FlightPrice&$skip=0&$top=10` |
|
|
684
|
+
| 11 | Expand | `GET Booking(…)?$select=…&$expand=_Travel($select=TravelID,TravelUUID)` |
|
|
685
|
+
| 12 | Update (changeset) | `PATCH Travel(…)`, `If-Match: W/"…"`, body `{"EndDate":"2026-12-24"}` |
|
|
686
|
+
| 13 | Creation with children (changeset) | `POST Travel`, body `{"TravelID":"00000042", …, "_Booking":[{…}]}` |
|
|
687
|
+
| 14 | Creation of a child (changeset) | `POST Travel(…)/_Booking` |
|
|
688
|
+
| 15 | Deletion (changeset) | `DELETE Booking(…)`, `If-Match: W/"…"` |
|
|
689
|
+
| 16 | Action | `POST Travel(…)/com.sap.gateway.srvd.zrt_ui_travel.v0001.acceptTravel`, `If-Match`, body `{}`, possibly followed by `?$select=…` |
|
|
690
|
+
| 17 | Error cases | Stale ETag → 412; missing `If-Match` → 428; unknown property in `$select` → 400; failed validation → 400 with `details[].target`; unknown key → 404 |
|