@open-rap/compiler 0.0.0-stage → 0.1.0

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.
Files changed (227) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +21 -2
  3. package/dist/agent/server.d.ts +35 -0
  4. package/dist/agent/server.js +408 -0
  5. package/dist/agent/server.js.map +1 -0
  6. package/dist/authoring/abapgit.d.ts +38 -0
  7. package/dist/authoring/abapgit.js +242 -0
  8. package/dist/authoring/abapgit.js.map +1 -0
  9. package/dist/authoring/catalog.d.ts +48 -0
  10. package/dist/authoring/catalog.js +117 -0
  11. package/dist/authoring/catalog.js.map +1 -0
  12. package/dist/authoring/floorplan.d.ts +28 -0
  13. package/dist/authoring/floorplan.js +31 -0
  14. package/dist/authoring/floorplan.js.map +1 -0
  15. package/dist/authoring/generate.d.ts +57 -0
  16. package/dist/authoring/generate.js +630 -0
  17. package/dist/authoring/generate.js.map +1 -0
  18. package/dist/authoring/mcp.d.ts +15 -0
  19. package/dist/authoring/mcp.js +52 -0
  20. package/dist/authoring/mcp.js.map +1 -0
  21. package/dist/authoring/new.d.ts +35 -0
  22. package/dist/authoring/new.js +389 -0
  23. package/dist/authoring/new.js.map +1 -0
  24. package/dist/bdef/ast.d.ts +249 -0
  25. package/dist/bdef/ast.js +3 -0
  26. package/dist/bdef/ast.js.map +1 -0
  27. package/dist/bdef/lexer.d.ts +105 -0
  28. package/dist/bdef/lexer.js +148 -0
  29. package/dist/bdef/lexer.js.map +1 -0
  30. package/dist/bdef/parser.d.ts +22 -0
  31. package/dist/bdef/parser.js +1012 -0
  32. package/dist/bdef/parser.js.map +1 -0
  33. package/dist/build.d.ts +26 -0
  34. package/dist/build.js +62 -0
  35. package/dist/build.js.map +1 -0
  36. package/dist/cds/adapter.d.ts +91 -0
  37. package/dist/cds/adapter.js +381 -0
  38. package/dist/cds/adapter.js.map +1 -0
  39. package/dist/cds/expression-ast.d.ts +65 -0
  40. package/dist/cds/expression-ast.js +330 -0
  41. package/dist/cds/expression-ast.js.map +1 -0
  42. package/dist/cli.d.ts +9 -0
  43. package/dist/cli.js +485 -0
  44. package/dist/cli.js.map +1 -0
  45. package/dist/dcl/parser.d.ts +59 -0
  46. package/dist/dcl/parser.js +338 -0
  47. package/dist/dcl/parser.js.map +1 -0
  48. package/dist/ddic/catalog.d.ts +17 -0
  49. package/dist/ddic/catalog.js +211 -0
  50. package/dist/ddic/catalog.js.map +1 -0
  51. package/dist/ddic/cds-types.d.ts +47 -0
  52. package/dist/ddic/cds-types.js +131 -0
  53. package/dist/ddic/cds-types.js.map +1 -0
  54. package/dist/ddic/table-source.d.ts +70 -0
  55. package/dist/ddic/table-source.js +500 -0
  56. package/dist/ddic/table-source.js.map +1 -0
  57. package/dist/ddic/xml.d.ts +65 -0
  58. package/dist/ddic/xml.js +201 -0
  59. package/dist/ddic/xml.js.map +1 -0
  60. package/dist/dev.d.ts +128 -0
  61. package/dist/dev.js +434 -0
  62. package/dist/dev.js.map +1 -0
  63. package/dist/diagnostics.d.ts +33 -0
  64. package/dist/diagnostics.js +53 -0
  65. package/dist/diagnostics.js.map +1 -0
  66. package/dist/gen/abap/derived-types.d.ts +26 -0
  67. package/dist/gen/abap/derived-types.js +323 -0
  68. package/dist/gen/abap/derived-types.js.map +1 -0
  69. package/dist/gen/abap/eml.d.ts +118 -0
  70. package/dist/gen/abap/eml.js +362 -0
  71. package/dist/gen/abap/eml.js.map +1 -0
  72. package/dist/gen/abap/enums.d.ts +13 -0
  73. package/dist/gen/abap/enums.js +74 -0
  74. package/dist/gen/abap/enums.js.map +1 -0
  75. package/dist/gen/abap/framework-http.d.ts +14 -0
  76. package/dist/gen/abap/framework-http.js +1049 -0
  77. package/dist/gen/abap/framework-http.js.map +1 -0
  78. package/dist/gen/abap/framework-query.d.ts +14 -0
  79. package/dist/gen/abap/framework-query.js +225 -0
  80. package/dist/gen/abap/framework-query.js.map +1 -0
  81. package/dist/gen/abap/framework-utils.d.ts +29 -0
  82. package/dist/gen/abap/framework-utils.js +3707 -0
  83. package/dist/gen/abap/framework-utils.js.map +1 -0
  84. package/dist/gen/abap/framework.d.ts +12 -0
  85. package/dist/gen/abap/framework.js +480 -0
  86. package/dist/gen/abap/framework.js.map +1 -0
  87. package/dist/gen/abap/generate.d.ts +40 -0
  88. package/dist/gen/abap/generate.js +238 -0
  89. package/dist/gen/abap/generate.js.map +1 -0
  90. package/dist/gen/abap/rewrite.d.ts +82 -0
  91. package/dist/gen/abap/rewrite.js +957 -0
  92. package/dist/gen/abap/rewrite.js.map +1 -0
  93. package/dist/gen/abap/shadow.d.ts +25 -0
  94. package/dist/gen/abap/shadow.js +175 -0
  95. package/dist/gen/abap/shadow.js.map +1 -0
  96. package/dist/gen/abap/tokens.d.ts +33 -0
  97. package/dist/gen/abap/tokens.js +87 -0
  98. package/dist/gen/abap/tokens.js.map +1 -0
  99. package/dist/gen/app/index.d.ts +25 -0
  100. package/dist/gen/app/index.js +322 -0
  101. package/dist/gen/app/index.js.map +1 -0
  102. package/dist/gen/csdl.d.ts +11 -0
  103. package/dist/gen/csdl.js +562 -0
  104. package/dist/gen/csdl.js.map +1 -0
  105. package/dist/gen/deploy.d.ts +12 -0
  106. package/dist/gen/deploy.js +53 -0
  107. package/dist/gen/deploy.js.map +1 -0
  108. package/dist/gen/docs/index.d.ts +35 -0
  109. package/dist/gen/docs/index.js +267 -0
  110. package/dist/gen/docs/index.js.map +1 -0
  111. package/dist/gen/docs/markdown.d.ts +23 -0
  112. package/dist/gen/docs/markdown.js +187 -0
  113. package/dist/gen/docs/markdown.js.map +1 -0
  114. package/dist/gen/docs/site.d.ts +17 -0
  115. package/dist/gen/docs/site.js +106 -0
  116. package/dist/gen/docs/site.js.map +1 -0
  117. package/dist/gen/keycloak.d.ts +21 -0
  118. package/dist/gen/keycloak.js +90 -0
  119. package/dist/gen/keycloak.js.map +1 -0
  120. package/dist/gen/launchpad/index.d.ts +48 -0
  121. package/dist/gen/launchpad/index.js +302 -0
  122. package/dist/gen/launchpad/index.js.map +1 -0
  123. package/dist/gen/launchpad/ui.d.ts +14 -0
  124. package/dist/gen/launchpad/ui.js +350 -0
  125. package/dist/gen/launchpad/ui.js.map +1 -0
  126. package/dist/gen/mcp.d.ts +23 -0
  127. package/dist/gen/mcp.js +244 -0
  128. package/dist/gen/mcp.js.map +1 -0
  129. package/dist/gen/sql-expressions.d.ts +12 -0
  130. package/dist/gen/sql-expressions.js +136 -0
  131. package/dist/gen/sql-expressions.js.map +1 -0
  132. package/dist/gen/sql.d.ts +38 -0
  133. package/dist/gen/sql.js +612 -0
  134. package/dist/gen/sql.js.map +1 -0
  135. package/dist/gen/ui/index.d.ts +7 -0
  136. package/dist/gen/ui/index.js +27 -0
  137. package/dist/gen/ui/index.js.map +1 -0
  138. package/dist/gen/ui/layout.d.ts +230 -0
  139. package/dist/gen/ui/layout.js +511 -0
  140. package/dist/gen/ui/layout.js.map +1 -0
  141. package/dist/gen/ui/static.d.ts +55 -0
  142. package/dist/gen/ui/static.js +2901 -0
  143. package/dist/gen/ui/static.js.map +1 -0
  144. package/dist/gen/ui/views.d.ts +10 -0
  145. package/dist/gen/ui/views.js +511 -0
  146. package/dist/gen/ui/views.js.map +1 -0
  147. package/dist/gen/vendor.d.ts +23 -0
  148. package/dist/gen/vendor.js +68 -0
  149. package/dist/gen/vendor.js.map +1 -0
  150. package/dist/gen/xml.d.ts +9 -0
  151. package/dist/gen/xml.js +42 -0
  152. package/dist/gen/xml.js.map +1 -0
  153. package/dist/index.d.ts +20 -0
  154. package/dist/index.js +20 -0
  155. package/dist/index.js.map +1 -0
  156. package/dist/input/project.d.ts +50 -0
  157. package/dist/input/project.js +128 -0
  158. package/dist/input/project.js.map +1 -0
  159. package/dist/ir/access-controls.d.ts +11 -0
  160. package/dist/ir/access-controls.js +148 -0
  161. package/dist/ir/access-controls.js.map +1 -0
  162. package/dist/ir/authorizations.d.ts +25 -0
  163. package/dist/ir/authorizations.js +178 -0
  164. package/dist/ir/authorizations.js.map +1 -0
  165. package/dist/ir/behaviors.d.ts +16 -0
  166. package/dist/ir/behaviors.js +548 -0
  167. package/dist/ir/behaviors.js.map +1 -0
  168. package/dist/ir/builder.d.ts +20 -0
  169. package/dist/ir/builder.js +505 -0
  170. package/dist/ir/builder.js.map +1 -0
  171. package/dist/ir/config.d.ts +25 -0
  172. package/dist/ir/config.js +130 -0
  173. package/dist/ir/config.js.map +1 -0
  174. package/dist/ir/dictionary.d.ts +54 -0
  175. package/dist/ir/dictionary.js +328 -0
  176. package/dist/ir/dictionary.js.map +1 -0
  177. package/dist/ir/entities.d.ts +58 -0
  178. package/dist/ir/entities.js +676 -0
  179. package/dist/ir/entities.js.map +1 -0
  180. package/dist/ir/expressions.d.ts +33 -0
  181. package/dist/ir/expressions.js +327 -0
  182. package/dist/ir/expressions.js.map +1 -0
  183. package/dist/ir/handlers.d.ts +63 -0
  184. package/dist/ir/handlers.js +398 -0
  185. package/dist/ir/handlers.js.map +1 -0
  186. package/dist/ir/number-ranges.d.ts +24 -0
  187. package/dist/ir/number-ranges.js +74 -0
  188. package/dist/ir/number-ranges.js.map +1 -0
  189. package/dist/ir/seed.d.ts +23 -0
  190. package/dist/ir/seed.js +78 -0
  191. package/dist/ir/seed.js.map +1 -0
  192. package/dist/ir/service.d.ts +21 -0
  193. package/dist/ir/service.js +96 -0
  194. package/dist/ir/service.js.map +1 -0
  195. package/dist/launchpad/config.d.ts +47 -0
  196. package/dist/launchpad/config.js +321 -0
  197. package/dist/launchpad/config.js.map +1 -0
  198. package/dist/launchpad/workspace.d.ts +48 -0
  199. package/dist/launchpad/workspace.js +166 -0
  200. package/dist/launchpad/workspace.js.map +1 -0
  201. package/dist/srvd/parser.d.ts +36 -0
  202. package/dist/srvd/parser.js +193 -0
  203. package/dist/srvd/parser.js.map +1 -0
  204. package/dist/standard/data.d.ts +18 -0
  205. package/dist/standard/data.js +534 -0
  206. package/dist/standard/data.js.map +1 -0
  207. package/dist/standard/index.d.ts +36 -0
  208. package/dist/standard/index.js +507 -0
  209. package/dist/standard/index.js.map +1 -0
  210. package/dist/version.d.ts +2 -0
  211. package/dist/version.js +4 -0
  212. package/dist/version.js.map +1 -0
  213. package/docs/AGENTS.md +79 -0
  214. package/docs/ANNOTATIONS.md +76 -0
  215. package/docs/DECISIONS.md +1526 -0
  216. package/docs/DEPLOYMENT.md +113 -0
  217. package/docs/DIAGNOSTICS.md +116 -0
  218. package/docs/FEATURES.md +287 -0
  219. package/docs/GUIDE.md +560 -0
  220. package/docs/KNOWN_GAPS.md +216 -0
  221. package/docs/MCP.md +84 -0
  222. package/docs/PLAN.md +690 -0
  223. package/docs/README.md +148 -0
  224. package/docs/REMOTE_APIS.md +121 -0
  225. package/docs/ROADMAP.md +35 -0
  226. package/docs/UTILITIES.md +95 -0
  227. 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 |