@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.
- 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 +1526 -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
|
@@ -0,0 +1,1526 @@
|
|
|
1
|
+
# Technical decisions
|
|
2
|
+
|
|
3
|
+
Decisions taken on 2026-11-09, after the owner of the project approved the principle ("take the
|
|
4
|
+
technical decisions you think are the best; all the features of a non-draft RAP BO must be
|
|
5
|
+
respected"). Each entry gives the decision, the reason and the consequence. Numbers D1 to D13
|
|
6
|
+
correspond to points 1 to 13 of §11 of PLAN.md. The milestones M1 to M6 are those of PLAN.md §8.
|
|
7
|
+
|
|
8
|
+
## D1 — BDEF grammar written with Chevrotain
|
|
9
|
+
|
|
10
|
+
- **Decision**: Chevrotain grammar for the BDEFs (managed behavior and projection); the BDEF
|
|
11
|
+
parser of abaplint is used as an oracle in the tests.
|
|
12
|
+
- **Why**: typed AST, positioned error messages, full control over the handling of the
|
|
13
|
+
out-of-scope constructs. The parser of abaplint only produces a generic tree.
|
|
14
|
+
- **Consequence**: two parsers to keep aligned; a test compares the entities, aliases and
|
|
15
|
+
operations each one finds.
|
|
16
|
+
|
|
17
|
+
## D2 — ABP: generated derived types and source-to-source rewrite
|
|
18
|
+
|
|
19
|
+
- **Decision**: for each managed BDEF, the compiler generates an ABAP interface
|
|
20
|
+
(`ZRAP_DT_<BDEF>`) containing the RAP derived types (`%tky`, `%control`, `FOR …` tables,
|
|
21
|
+
`FAILED/REPORTED/MAPPED` responses, authorization and feature control requests and results),
|
|
22
|
+
then rewrites the behavior pool before giving it to the transpiler: signatures of the handlers,
|
|
23
|
+
`TYPE TABLE|STRUCTURE|RESPONSE FOR …` references, removal of `FOR BEHAVIOR OF`, and **all** the
|
|
24
|
+
EML statements (`READ`, `MODIFY`, `COMMIT`, `ROLLBACK ENTITIES`, long and short forms) lowered
|
|
25
|
+
into calls of the kernel class `ZRAP_EML` (`begin`, operations, `end`). The `#` of the operands
|
|
26
|
+
become the expected derived type; the inline declarations (`RESULT DATA(x)`) are hoisted as
|
|
27
|
+
`DATA x TYPE …`.
|
|
28
|
+
- **Adjustment (M4)**: the EML is parsed by a token-based parser of the project
|
|
29
|
+
(`gen/abap/eml.ts`), and no longer left to the lowering of the transpiler: abaplint does not
|
|
30
|
+
recognize all the forms (`CREATE AUTO FILL CID SET FIELDS WITH` is an unknown statement for
|
|
31
|
+
it), and the lowering of the transpiler lost `IN LOCAL MODE`, `BY \_assoc`, `LINK` and
|
|
32
|
+
`CREATE BY`. The complete ABAP check (types included) runs on the rewritten sources, with the
|
|
33
|
+
configuration of the transpiler; each replacement keeps the number of lines of the replaced
|
|
34
|
+
text, so that the errors (ABP004) point to the original line.
|
|
35
|
+
- **Adjustment (feature control)**: as on SAP, the feature control requests and results
|
|
36
|
+
(`FOR INSTANCE FEATURES`, `FOR GLOBAL FEATURES`) only contain what the BDEF declares at the
|
|
37
|
+
matching level: `%create`, `%update`, `%delete` of the `( features : … )` operations,
|
|
38
|
+
`%action-x` of the `( features : … )` actions, `%assoc-_x` of the `create ( features : … )`,
|
|
39
|
+
`%field-x` of the `field ( features : instance )` fields. A pool that writes `%update` without
|
|
40
|
+
`update ( features : instance )` is refused (ABP004) instead of passing `rapt check` and then
|
|
41
|
+
failing on activation on SAP.
|
|
42
|
+
- **Why**: abaplint types these constructs as `void` and the transpiler then refuses the ABP.
|
|
43
|
+
The PoC (PLAN.md, V10) proved the approach with the published packages; M4 generalized it.
|
|
44
|
+
- **Consequence**: the transpiler and the runtime are never patched nor monkeypatched; they are
|
|
45
|
+
given standard ABAP. The methods of `ZRAP_EML`, a "clean-room" class of the project, are
|
|
46
|
+
replaced at runtime by the JavaScript kernel (`runtime/src/rap/kernel.ts`). Any construct the
|
|
47
|
+
pass cannot handle produces a diagnostic (ABP005 to ABP007) and an entry in KNOWN_GAPS.md. An
|
|
48
|
+
upstream contribution (derived types in abaplint) remains desirable; it is public and will
|
|
49
|
+
only be proposed with the agreement of the owner of the project.
|
|
50
|
+
|
|
51
|
+
## D3 — Fastify rather than Express
|
|
52
|
+
|
|
53
|
+
- **Why**: `inject()` lets the server be tested without a network; encapsulated plugins; good
|
|
54
|
+
TypeScript typing; performance.
|
|
55
|
+
|
|
56
|
+
## D4 — OData reads through generated SQL views
|
|
57
|
+
|
|
58
|
+
- **Decision**: each CDS view becomes a PostgreSQL view (`zr_*` on the tables, then `zc_*` on
|
|
59
|
+
`zr_*`). The OData reads query the `zc_*` views; the writes go through the RAP runtime, to the
|
|
60
|
+
tables.
|
|
61
|
+
- **Why**: it is the separation of RAP (query through the CDS projection, transaction through
|
|
62
|
+
the BO). Future CDS expressions (CASE, CAST…) will translate directly into SQL.
|
|
63
|
+
|
|
64
|
+
## D5 — OData scope extended to what a V4 List Report / Object Page sends
|
|
65
|
+
|
|
66
|
+
- **Decision**: in addition to the initial list: navigation `Entity(…)/_Assoc` (read and
|
|
67
|
+
creation), `ge`, `le`, `not`, `startswith`, `endswith`, `/$count` segment, simple `$search`,
|
|
68
|
+
CSRF token, `IEEE754Compatible=true`, one-level deep insert, `sap-messages` header,
|
|
69
|
+
`$<Content-ID>` references in a changeset, `sap-*` parameters ignored.
|
|
70
|
+
- **Why**: without them, a Fiori Elements V4 application (or our OpenUI5 UI, which uses the same
|
|
71
|
+
ODataModel) does not fully work.
|
|
72
|
+
|
|
73
|
+
## D6 — Path of the service modeled on RAP
|
|
74
|
+
|
|
75
|
+
- **Decision**: by default `/sap/opu/odata4/sap/<binding>/srvd/sap/<srvd>/0001/`, configurable.
|
|
76
|
+
- **Why**: an existing Fiori Elements application can point to the service without changing its
|
|
77
|
+
manifest.
|
|
78
|
+
|
|
79
|
+
## D7 — Fixture activatable as is on a real system
|
|
80
|
+
|
|
81
|
+
- **Decision**: the `fixtures/travel` fixture is a complete and consistent abapGit export
|
|
82
|
+
(strict(2), global authorization, managed numbering…). The authorization is no longer ignored:
|
|
83
|
+
it is part of the supported non-draft features (D14).
|
|
84
|
+
- **Why**: the reference behavior can be checked on an SAP system.
|
|
85
|
+
|
|
86
|
+
## D8 — Timestamps as `timestamptz(6)`
|
|
87
|
+
|
|
88
|
+
- **Decision**: `TIMESTAMPL`/`UTCLONG` → `timestamptz(6)`, `TIMESTAMP` → `timestamptz(0)`.
|
|
89
|
+
- **Why**: natural type in SQL and for the OData filters. The timestamps generated by the runtime
|
|
90
|
+
are to the microsecond, which keeps the ETags stable.
|
|
91
|
+
- **Consequence**: the seventh ABAP decimal is lost (KNOWN_GAPS.md).
|
|
92
|
+
- **Adjustment (M4)**: `@abaplint/runtime` 2.14.24 stores the packed numbers in an exact form
|
|
93
|
+
(scaled `BigInt` integer). The conversions between the buffer and ABAP go through text
|
|
94
|
+
(`toFixed`): a `TIMESTAMPL` reaches the ABP intact and comes back intact. Only the arithmetic
|
|
95
|
+
operations of the runtime on packed numbers go through JavaScript numbers (KNOWN_GAPS.md). The
|
|
96
|
+
ETags are canonical values of the buffer, never recomputed by the ABP.
|
|
97
|
+
|
|
98
|
+
## D9 — Client removed
|
|
99
|
+
|
|
100
|
+
- **Decision**: the `CLNT` column (`CLIENT`, `MANDT`) is removed from the tables; single-tenant
|
|
101
|
+
application.
|
|
102
|
+
|
|
103
|
+
## D10 — Freestyle OpenUI5 1.136 LTS UI
|
|
104
|
+
|
|
105
|
+
- **Decision**: `sap.m`, `sap.f`, `sap.uxap`, `sap.ui.layout` controls, `sap_horizon` theme,
|
|
106
|
+
ODataModel V4 with `autoExpandSelect`. `sap.ui.mdc` is a possible evolution.
|
|
107
|
+
- **Why**: Apache-2.0 libraries; the ODataModel V4 is the one of Fiori Elements V4.
|
|
108
|
+
- **Implementation (M5)**: `rapt build` writes `ui/`: one List Report XML view per root entity
|
|
109
|
+
set and one Object Page view per entity set, generated from the `@UI` annotations (same
|
|
110
|
+
derivation as the CSDL), and generic controllers driven by `ui/model/layout.json` (filters,
|
|
111
|
+
search, non-draft creation, edit and save with `submitBatch`, deletion, actions with a
|
|
112
|
+
parameter dialog, factory actions, editable composition tables, messages). The feature control
|
|
113
|
+
goes through `__OperationControl`, `__EntityControl` and `__FieldControl`, which the ODataModel
|
|
114
|
+
selects itself.
|
|
115
|
+
|
|
116
|
+
## D11 — Serialized ABP execution, reentrant within a LUW
|
|
117
|
+
|
|
118
|
+
- **Decision**: a lock per process wraps the ABP calls; it is reentrant for the LUW that holds
|
|
119
|
+
it (a determination that runs an internal action does not block itself).
|
|
120
|
+
- **Why**: the abaplint runtime has a global state (`sy-*`, static attributes).
|
|
121
|
+
- **Consequence**: scaling with processes or containers.
|
|
122
|
+
- **Implementation (M4)**: `RapRuntime.transaction` chains the LUWs on a promise (one LUW at a
|
|
123
|
+
time per process); the current LUW is carried by `AsyncLocalStorage`, which makes the EML calls
|
|
124
|
+
of the handlers reentrant. Between processes, the locks are PostgreSQL row locks
|
|
125
|
+
(`SELECT … FOR UPDATE NOWAIT` on the instance of the `lock master`, cause `locked` when refused).
|
|
126
|
+
|
|
127
|
+
## D12 — Naming and license
|
|
128
|
+
|
|
129
|
+
- **Decision**: packages `@open-rap/model`, `@open-rap/compiler`,
|
|
130
|
+
`@open-rap/runtime`; `rapt` CLI; MIT license for this repository.
|
|
131
|
+
|
|
132
|
+
## D13 — Test corpus of the parsers
|
|
133
|
+
|
|
134
|
+
- **Initial decision**: an extract of BDEF and CDS sources of the SAP-samples repositories
|
|
135
|
+
(Apache-2.0).
|
|
136
|
+
- **Adjustment (M1)**: the SAP-samples repositories were reorganized and their paths can no
|
|
137
|
+
longer be found by code search. The corpus is therefore written for the project
|
|
138
|
+
(`packages/compiler/test/bdef-parser.test.ts`): namespaced names, operation options, static,
|
|
139
|
+
factory and internal actions, functions, side effects, `control` mappings, and the
|
|
140
|
+
out-of-scope constructs (draft, late numbering, events…). The same sources are compared with
|
|
141
|
+
the BDEF parser of abaplint (D1). An SAP-samples extract may be added later.
|
|
142
|
+
|
|
143
|
+
## D14 — Functional scope: a complete non-draft managed RAP BO
|
|
144
|
+
|
|
145
|
+
- **Decision**: the target is the set of features of a managed BO without draft, tracked in
|
|
146
|
+
[FEATURES.md](FEATURES.md). It adds to the initial MVP: authorizations (global and instance),
|
|
147
|
+
static and dynamic feature control, external numbering, static, factory, internal actions and
|
|
148
|
+
actions with a parameter, on save determinations, precheck, side effects (CSDL), T100 messages,
|
|
149
|
+
reads and creations by association in the ABP.
|
|
150
|
+
- **Still out of scope**, with their reason in FEATURES.md: draft (excluded by the
|
|
151
|
+
specification, then added by D54), unmanaged implementation, late numbering, events, change
|
|
152
|
+
documents, extensions, analytics. `with additional save` and `with unmanaged save`, excluded at
|
|
153
|
+
first, are supported since D30.
|
|
154
|
+
|
|
155
|
+
## D15 — Generic runtime driven by the IR
|
|
156
|
+
|
|
157
|
+
- **Decision**: the generated application contains a generic runtime (`@open-rap/runtime`)
|
|
158
|
+
that reads `model.json`. Only the SQL, the CSDL, the JavaScript of the ABP, the UI and the
|
|
159
|
+
packaging are generated. The runtime is bundled as a tarball in `vendor/`.
|
|
160
|
+
- **Why**: a single runtime tested for all BOs; a standalone output without a private registry.
|
|
161
|
+
- **Adjustment (M6)**: rather than a tarball, `vendor/` contains the compiled `runtime` and
|
|
162
|
+
`model` packages (`dist/` and a production `package.json`); their version carries a hash of
|
|
163
|
+
their content, so that `npm install` updates them after a new `rapt build`. The `.npmrc`
|
|
164
|
+
(`install-links=true`) installs them as ordinary packages. `server.mjs` calls `start()` of the
|
|
165
|
+
runtime, configured by environment variables (`.env.example`).
|
|
166
|
+
|
|
167
|
+
## D16 — Canonical representation of the values
|
|
168
|
+
|
|
169
|
+
- **Decision**: in the transactional buffer, the values are canonical JavaScript values: strings
|
|
170
|
+
for CHAR, NUMC and STRING (trailing blanks removed); strings for decimals and INT8 (no loss of
|
|
171
|
+
precision); `YYYY-MM-DD` for dates (initial = `null`); `HH:MM:SS` for times; ISO 8601 UTC to
|
|
172
|
+
the microsecond for timestamps; lower-case UUIDs with dashes; booleans for `ABAP_BOOLEAN`.
|
|
173
|
+
- **Why**: a single pivot form between ABAP, SQL and OData; the conversions are isolated and
|
|
174
|
+
tested.
|
|
175
|
+
|
|
176
|
+
## D17 — Toolchain
|
|
177
|
+
|
|
178
|
+
- **Decision**: TypeScript 7.0 (`strict`, `noUncheckedIndexedAccess`,
|
|
179
|
+
`exactOptionalPropertyTypes`, `verbatimModuleSyntax`), ESM `NodeNext` with `.js` specifiers,
|
|
180
|
+
`tsc` compilation to `dist/`, Vitest (aliases to the sources), pnpm workspaces.
|
|
181
|
+
|
|
182
|
+
## D18 — Database of the tests
|
|
183
|
+
|
|
184
|
+
- **Decision**: PGlite (PostgreSQL in WASM, in process) for the integration tests; a real
|
|
185
|
+
PostgreSQL for the e2e.
|
|
186
|
+
|
|
187
|
+
## D19 — "Shadow" DDIC for the transpiler
|
|
188
|
+
|
|
189
|
+
- **Decision**: the registry given to the transpiler receives the tables of the model with
|
|
190
|
+
predefined types (DATATYPE, LENG, DECIMALS), the data elements of the standard catalog and of
|
|
191
|
+
the project; no CDS views.
|
|
192
|
+
- **Why**: the SQL layer of the transpiler fails on associations and does not know the standard
|
|
193
|
+
SAP data elements; the types of the ABP come from the derived interface (D2); our SQL comes
|
|
194
|
+
from the IR.
|
|
195
|
+
- **Consequence**: a `TYPE zr_travel` type (CDS view) was not available in the ABP; the derived
|
|
196
|
+
types and the tables were.
|
|
197
|
+
- **Adjusted (D26)**: the CDS entities are given as "shadow" tables with the same name and
|
|
198
|
+
elements (predefined types, without associations), for ABAP SQL; `TYPE zr_travel` is therefore
|
|
199
|
+
typed. The shadow tables no longer have a client column (D9). The CDS views themselves stay out
|
|
200
|
+
of the registry of the transpiler.
|
|
201
|
+
|
|
202
|
+
## D20 — `field ( mandatory )` without a check at runtime
|
|
203
|
+
|
|
204
|
+
- **Decision**: as in RAP, no runtime check; `Common.FieldControl` Mandatory in the CSDL and
|
|
205
|
+
required field in the UI. `mandatory : create`, on the other hand, is checked on creation, as
|
|
206
|
+
in RAP.
|
|
207
|
+
|
|
208
|
+
## D21 — Triggering of the determinations and validations
|
|
209
|
+
|
|
210
|
+
- **Decision**: the on modify determinations run at the end of each modifying request, in
|
|
211
|
+
successive rounds: a round handles the instances whose trigger was touched since the previous
|
|
212
|
+
run of this determination; a determination is not triggered again by its own changes; the loop
|
|
213
|
+
is bounded (error beyond 20 rounds). The validations and the on save determinations run in the
|
|
214
|
+
save sequence, on the accumulated changes of the LUW.
|
|
215
|
+
|
|
216
|
+
## D22 — RAP failure causes and HTTP statuses
|
|
217
|
+
|
|
218
|
+
- **Decision**: the first entry of `failed` gives the status of the OData error: `unspecific`
|
|
219
|
+
400, `unauthorized` 403, `not_found` 404, `locked` and `conflict` 409, `readonly` 400,
|
|
220
|
+
`disabled` 409, `dependency` 424; a missing `If-Match` on PATCH, PUT and DELETE of an entity
|
|
221
|
+
with an ETag gives 428, a stale ETag 412. The main message is the first error message of
|
|
222
|
+
`reported`; the other messages go into `details`. Without a message, a generic text describes
|
|
223
|
+
the cause ("Travel(…) is locked by another user").
|
|
224
|
+
- **Why**: SAP does not publish a mapping table; these statuses are those of the HTTP protocol
|
|
225
|
+
for each cause, and Fiori elements shows the message whatever the status.
|
|
226
|
+
|
|
227
|
+
## D23 — One LUW per request or per change set; responses rendered after the commit
|
|
228
|
+
|
|
229
|
+
- **Decision**: a modifying request (or a change set of a `$batch`) is a LUW: the operations, the
|
|
230
|
+
on modify determinations at the end of each request (D21), then the save sequence, in a
|
|
231
|
+
PostgreSQL transaction. The responses (created entity, result of an action…) are read after the
|
|
232
|
+
commit in the projection views; in a change set, they are deferred until the end of the change
|
|
233
|
+
set. A failure rolls back the whole change set, which receives a single error response.
|
|
234
|
+
- **Why**: the response shows the saved state (determinations, administrative fields, ETag), as
|
|
235
|
+
in RAP, and the reads never have to see the unsaved buffer. The navigations of a write
|
|
236
|
+
(`$1/_Booking`, `Travel(…)/_Booking(…)`) are resolved in the LUW, which sees the instances
|
|
237
|
+
created earlier in the change set.
|
|
238
|
+
|
|
239
|
+
## D24 — Extended "clean-room" ABAP base
|
|
240
|
+
|
|
241
|
+
- **Decision**: in addition to `if_abap_behv`, `cl_abap_behavior_handler` and
|
|
242
|
+
`cl_abap_context_info`, the compiler provides `if_message`, `if_t100_message`,
|
|
243
|
+
`if_t100_dyn_msg`, `if_abap_behv_message`, `cx_root` (and `cx_static_check`,
|
|
244
|
+
`cx_dynamic_check`, `cx_no_check`, the classes of the runtime errors), `cl_system_uuid`, the
|
|
245
|
+
`scx_t100key` structure and the standard data elements of the DDIC catalog. The text of an
|
|
246
|
+
exception is its T100 message, its attributes resolved by name (native method
|
|
247
|
+
`ZRAP_EML=>exception_text`).
|
|
248
|
+
- **Why**: real behavior pools use message exception classes (pattern
|
|
249
|
+
`/DMO/CM_FLIGHT_MESSAGES`), `cl_system_uuid` and these types. The transpiler turns any class
|
|
250
|
+
named `CX_ROOT` into a subclass of the JavaScript `Error`: it is enough to provide it.
|
|
251
|
+
- **Consequence**: these objects are written from the public signatures of SAP (LICENSES.md); an
|
|
252
|
+
object of the project with the same name takes precedence.
|
|
253
|
+
|
|
254
|
+
## D25 — Checks of the OData interface of the writes
|
|
255
|
+
|
|
256
|
+
- **Decision**: the `readonly` properties, the managed numbering properties and the
|
|
257
|
+
administrative fields sent by an OData client are ignored (as `Core.Computed` provides); an
|
|
258
|
+
unknown property gives 400. The operations that are not exposed (`use create` missing,
|
|
259
|
+
`internal` operation) give 405. The keys of an entity do not change with PATCH (400). The
|
|
260
|
+
feature control (`__EntityControl`, `__OperationControl`, `__FieldControl`) is only computed
|
|
261
|
+
when the request selects these properties (or has no `$select`). The global feature control of
|
|
262
|
+
`update`, `delete` and the instance actions is also there, with the same value for all the
|
|
263
|
+
instances: RAP presents it like the instance feature control ("consumer hint"). In the
|
|
264
|
+
generated views, these values are read with `%{…}` (raw value): `${…}` would give the text
|
|
265
|
+
formatted by the OData type (`"1"` for an Edm.Byte), and the comparison would fail.
|
|
266
|
+
- **Why**: it is the behavior the ODataModel V4 of Fiori elements expects; computing the feature
|
|
267
|
+
control calls the ABP and must not slow down the reads that do not use it.
|
|
268
|
+
|
|
269
|
+
## D26 — Read ABAP SQL in the ABP
|
|
270
|
+
|
|
271
|
+
- **Initial decision**: any ABAP SQL statement in a behavior pool was a compile error (ABP007),
|
|
272
|
+
for lack of a database connection for the abaplint runtime.
|
|
273
|
+
- **Revised decision**: `SELECT` (simple, `SINGLE`, `INTO TABLE`, `FOR ALL ENTRIES`, joins,
|
|
274
|
+
aggregates, subqueries, `SELECT … ENDSELECT` with `SELECT *`) reads the tables and the CDS
|
|
275
|
+
entities of the project. The writes (`INSERT`, `UPDATE`, `MODIFY`, `DELETE` on a table) are
|
|
276
|
+
refused in the handlers (ABP007): a managed BO is written by the RAP runtime, the code changes
|
|
277
|
+
it with EML. They are allowed in the saver (`save_modified`) of a BO with additional or
|
|
278
|
+
unmanaged save (D30). Common table expressions (`WITH`) stay refused.
|
|
279
|
+
- **Implementation**:
|
|
280
|
+
- the SQL `@abaplint/transpiler` generates assumes columns in ABAP format (its own schemas store
|
|
281
|
+
dates as `NCHAR(8)`, UUIDs as 32 hexadecimal digits). The generated schema therefore adds a
|
|
282
|
+
`rapt_abap` schema of views, one per table (without a client column, D9) and per CDS entity,
|
|
283
|
+
lower-case column names: dates `YYYYMMDD`, times `HHMMSS`, UUIDs and binaries in upper-case
|
|
284
|
+
hexadecimal, booleans `X`/space, timestamps as packed numbers (`YYYYMMDDhhmmss.fffffff`), ABAP
|
|
285
|
+
initial values instead of `NULL`s. Fixed-length character types become `bpchar`, whose
|
|
286
|
+
comparisons ignore trailing blanks like ABAP. `abap.dbo.schemaPrefix` (option of the abaplint
|
|
287
|
+
runtime) makes the transpiled code read these views;
|
|
288
|
+
- the runtime registers a read-only database client (`DatabaseClient` of the abaplint runtime)
|
|
289
|
+
that runs the query in the transaction of the current LUW, after a translation to PostgreSQL:
|
|
290
|
+
`UP TO n ROWS` → `LIMIT`, `ORDER BY PRIMARY KEY`, `ASCENDING`, operators `EQ`/`GE`…,
|
|
291
|
+
`alias~field`, lower-case identifiers. Only the views of `rapt_abap` are readable. Each query
|
|
292
|
+
runs in a savepoint: an SQL error leaves the transaction of the LUW usable.
|
|
293
|
+
`COMMIT WORK`/`ROLLBACK WORK` are a runtime error, as in RAP;
|
|
294
|
+
- the CDS entities are given to the transpiler as "shadow" tables with the same name and
|
|
295
|
+
elements (D19): `SELECT FROM zr_travel` and `DATA ls TYPE zr_travel` are typed;
|
|
296
|
+
- abaplint does not type the inline declaration of the result of a literal, of an aggregate
|
|
297
|
+
other than `COUNT(*)` or of an expression: the transpiled method would fail on start. The
|
|
298
|
+
rewriting pass declares the two most common forms (existence check
|
|
299
|
+
`SELECT SINGLE @abap_true … INTO @DATA(x)`: `abap_boolean`; `MAX( col )`/`MIN( col )` of a
|
|
300
|
+
single source, numbering pattern of the RAP100 tutorial: the type of the column); the others
|
|
301
|
+
are reported at compile time (ABP009);
|
|
302
|
+
- `MESSAGE … INTO` takes its texts from the message classes of the project.
|
|
303
|
+
- **Semantics**: as in RAP, `SELECT` reads the database, not the transactional buffer: the
|
|
304
|
+
unsaved changes of the LUW are not visible in it (read the BO with EML for that).
|
|
305
|
+
- **Why**: reading control tables with SQL, the uniqueness of an identifier or numbering with
|
|
306
|
+
`MAX` are common in real behavior pools; views in ABAP format leave the SQL of the transpiler
|
|
307
|
+
almost intact and give the ABAP semantics of the comparisons. Trade-off: a condition on a
|
|
308
|
+
converted column (date, UUID, timestamp) cannot use an index (KNOWN_GAPS.md).
|
|
309
|
+
- **Related fix**: the failure of a lock (`FOR UPDATE NOWAIT`) left the PostgreSQL transaction
|
|
310
|
+
of the LUW in an error state; it is also isolated in a savepoint (test `rap-locks.test.ts` on a
|
|
311
|
+
real PostgreSQL).
|
|
312
|
+
|
|
313
|
+
## D27 — OpenUI5 resources served from the npm packages
|
|
314
|
+
|
|
315
|
+
- **Decision**: the runtime serves `/resources/` from the npm packages of OpenUI5 (`@openui5/*`
|
|
316
|
+
1.136, Apache-2.0): the sources of the libraries as they are, and the themes compiled on the
|
|
317
|
+
first request with `less-openui5` (Apache-2.0), then kept in memory. `/ui/` serves the
|
|
318
|
+
generated application.
|
|
319
|
+
- **Why**: the OpenUI5 CDN is not always reachable (it is not from the development environment
|
|
320
|
+
of this project) and a standalone application must not depend on it; npm is enough, without
|
|
321
|
+
UI5 tooling on startup. The first load requests about 400 modules: a bundle built by the UI5
|
|
322
|
+
tooling (`ui5 build`) is planned for the Docker image (M6).
|
|
323
|
+
|
|
324
|
+
## D28 — OIDC sessions without an additional dependency
|
|
325
|
+
|
|
326
|
+
- **Decision**: the "authorization code" flow with PKCE is written with `jose` (already used for
|
|
327
|
+
the bearer tokens): sign-in state and session in signed cookies (HMAC-SHA256, HttpOnly,
|
|
328
|
+
SameSite=Lax), without storage on the server; CSRF token bound to the session; discovery
|
|
329
|
+
possible on an internal URL (`OIDC_DISCOVERY_URL`) distinct from the public issuer. The tests
|
|
330
|
+
use an OIDC provider written for them (discovery, authorization, token, JWKS).
|
|
331
|
+
- **Adjustment of PLAN.md**: `openid-client`, `@fastify/secure-session` and `oauth2-mock-server`
|
|
332
|
+
are not added. `openid-client` requires the issuer to be the discovery URL, which does not suit
|
|
333
|
+
Keycloak in docker-compose (the browser sees `localhost:8180`, the container of the
|
|
334
|
+
application `keycloak:8080`).
|
|
335
|
+
- **Consequence**: the session lasts 8 hours (`maxAge`); there is neither token refresh nor
|
|
336
|
+
revocation on the server before expiry (KNOWN_GAPS.md).
|
|
337
|
+
|
|
338
|
+
## D29 — End-to-end test and CI
|
|
339
|
+
|
|
340
|
+
- **Decision**: the e2e test (`packages/runtime/test/e2e.test.ts`) starts from the compiled CLI:
|
|
341
|
+
`rapt build` of the fixture, `npm install` of the generated application, `node server.mjs` on a
|
|
342
|
+
real PostgreSQL (the one of `RAPT_E2E_DATABASE_URL`, otherwise a server started for the test,
|
|
343
|
+
otherwise PGlite), then the OData service and the UI in Chromium. The CI provides a
|
|
344
|
+
PostgreSQL 16 service and Chromium. A second job builds the image and starts the generated
|
|
345
|
+
`docker-compose.yml` (`--wait`, HTTP checks of the service and of the UI). Also checked in the
|
|
346
|
+
development environment: image built, PostgreSQL and application started by compose, creation,
|
|
347
|
+
creation by association, action and UI served by the container.
|
|
348
|
+
- **Why**: it is the delivered artifact that is tested, not the sources of the runtime.
|
|
349
|
+
- **Note**: TypeScript 7.0.2 compiles `new (x as T).Builder()` without the parentheses; only the
|
|
350
|
+
compiled JavaScript (dist) shows it, Vitest compiling the sources differently. The e2e test,
|
|
351
|
+
which runs the dist, revealed it; the code avoids this form.
|
|
352
|
+
|
|
353
|
+
## D30 — Additional and unmanaged save in a managed BO
|
|
354
|
+
|
|
355
|
+
- **Decision**: `managed with additional save` and `managed with unmanaged save` (header of the
|
|
356
|
+
BDEF, or `with additional save` / `with unmanaged save` per entity, `with full data` accepted)
|
|
357
|
+
are supported, with the local saver class of the behavior pool (`lsc_…` inheriting from
|
|
358
|
+
`cl_abap_behavior_saver` or `cl_abap_behavior_saver_failed`): `save_modified` (mandatory) and
|
|
359
|
+
`cleanup`. Additional save: the runtime writes the persistent tables, then calls
|
|
360
|
+
`save_modified`. Unmanaged save: the entity has no persistent table, the runtime reads and
|
|
361
|
+
locks the instances through the CDS view and `save_modified` writes them.
|
|
362
|
+
- **Implementation**:
|
|
363
|
+
- derived types `TYPE TABLE FOR CHANGE`, `TYPE REQUEST FOR CHANGE` and
|
|
364
|
+
`TYPE REQUEST FOR DELETE`; the rewriting pass declares `save_modified` with its parameters
|
|
365
|
+
(`create`, `update`, `delete`, `reported`, and `failed` for `cl_abap_behavior_saver_failed`);
|
|
366
|
+
`adjust_numbers` (late numbering) is refused (ABP007);
|
|
367
|
+
- ABAP SQL writes are only allowed in the saver class (ABP007 elsewhere) and, at runtime,
|
|
368
|
+
during `save_modified` only. The RAP-specific forms
|
|
369
|
+
(`… FROM [TABLE] @source [INDICATORS SET STRUCTURE %control] MAPPING FROM ENTITY`), which the
|
|
370
|
+
transpiler does not know, are lowered to the kernel, which applies the mapping of the BDEF
|
|
371
|
+
and writes with the types of the database, exact values;
|
|
372
|
+
- the other writes go through the transpiler and the views of the `rapt_abap` schema (D26): an
|
|
373
|
+
`INSTEAD OF` trigger per table writes the table, values converted back from their ABAP form
|
|
374
|
+
(functions `rapt_abap.to_date`, `to_uuid`, `to_timestamp`…);
|
|
375
|
+
- the rows of `update` carry all the values (like `with full data`), `%control` the changed
|
|
376
|
+
fields; `delete` also contains the compositions deleted in cascade;
|
|
377
|
+
- a `failed` of the saver (`cl_abap_behavior_saver_failed`) rolls back the LUW, like a
|
|
378
|
+
validation; the messages of `reported` are returned to the client;
|
|
379
|
+
- all the transparent tables of the project enter the model (database and types of the
|
|
380
|
+
transpiler), not only those of the CDS views and of the persistent tables: a log or control
|
|
381
|
+
table can be read and written by ABAP SQL. A table no entity uses and that cannot be built is
|
|
382
|
+
left aside (information DDIC005).
|
|
383
|
+
- **Transpiler defect worked around**: `DELETE dbtab FROM @wa` makes it lose the row, and the
|
|
384
|
+
query would delete the whole table: refused at compile time (ABP007) and, for safety, a
|
|
385
|
+
`DELETE` without a condition is refused at runtime.
|
|
386
|
+
- **Why**: it is the RAP way to write existing tables or to log the changes of a BO, frequent in
|
|
387
|
+
real projects; the ABAP SQL of the saver reuses the views of D26.
|
|
388
|
+
|
|
389
|
+
## D31 — Consuming remote APIs in the ABP (HTTP, OData client proxy, SCM and SCM2)
|
|
390
|
+
|
|
391
|
+
- **Decision**: a behavior pool can call remote APIs with the APIs of ABAP Cloud, rewritten in
|
|
392
|
+
clean-room from their public signatures:
|
|
393
|
+
- HTTP client: `cl_http_destination_provider` (`create_by_url`,
|
|
394
|
+
`create_by_comm_arrangement`, `create_by_cloud_destination`), `cl_web_http_client_manager`,
|
|
395
|
+
`if_web_http_client`, `if_web_http_request` (path, query, headers, text or binary body, form,
|
|
396
|
+
basic or bearer authentication), `if_web_http_response`, `if_web_http_header`,
|
|
397
|
+
`if_a4c_cp_service`;
|
|
398
|
+
- OData client proxy of the service consumption model: `/iwbep/cl_cp_factory_remote` and
|
|
399
|
+
`cl_web_odata_client_factory` (`create_v2_remote_proxy`, `create_v4_remote_proxy`); list read
|
|
400
|
+
(`create_by_range` of the filters combined with `and`/`or`/`not`, `$select`, `$orderby`,
|
|
401
|
+
`$top`, `$skip`, `request_count`, `request_no_business_data`), read by key
|
|
402
|
+
(`navigate_with_key`), create, update (`PATCH`/`PUT`, provided properties, `set_if_match`),
|
|
403
|
+
delete;
|
|
404
|
+
- exceptions `cx_http_dest_provider_error`, `cx_web_http_client_error`,
|
|
405
|
+
`cx_web_message_error`, `/iwbep/cx_gateway`, `/iwbep/cx_cp_remote` (`http_status_code`,
|
|
406
|
+
`http_error_body`), with the text of the error for `get_text( )`.
|
|
407
|
+
- **Destinations**: on SAP, they come from the destination service of BTP or from a
|
|
408
|
+
communication arrangement; the generated application reads them from its environment:
|
|
409
|
+
`DESTINATION_<NAME>_URL`, then `_USER` and `_PASSWORD` (basic), `_TOKEN` (bearer) or
|
|
410
|
+
`_HEADERS` (JSON object: API key…). `<NAME>` is the name of `create_by_cloud_destination`, or
|
|
411
|
+
`<SCENARIO>_<SERVICE_ID>` then `<SCENARIO>` for `create_by_comm_arrangement`
|
|
412
|
+
(`comm_system_id` ignored). The credentials stay in the kernel: the ABAP only sees the URL.
|
|
413
|
+
`rapt build` lists the destinations named by literals in the `.env.example` and the README of
|
|
414
|
+
the application.
|
|
415
|
+
- **Service consumption model**:
|
|
416
|
+
- SCM2: the consumption model class (`INHERITING FROM /iwbep/cl_v4_abs_pm_model_prov`) is kept
|
|
417
|
+
for its types (`tys_…`, `tyt_…`) and its constants (`gcs_entity_set`, `gcs_entity_type`); the
|
|
418
|
+
rewriting pass removes the definition of the model (`define`, `def_…`), written with the
|
|
419
|
+
`/iwbep/if_v4_pm_*` interfaces of SAP Gateway;
|
|
420
|
+
- SCM: the generated abstract entities (`_vc` value control fields included) type the data;
|
|
421
|
+
they are structures for the ABAP (shadow DDIC). The service definition generated for the
|
|
422
|
+
client proxy has no binding: when there are several definitions, the one with a binding is
|
|
423
|
+
built (SRVD007);
|
|
424
|
+
- the proxy reads the `$metadata` of the remote service once: entity sets, keys, types of the
|
|
425
|
+
properties. The internal ABAP names (`TRAVEL_ID`, `'TRAVEL'`) are matched with the EDM names
|
|
426
|
+
ignoring case and `_` (`TravelID`). The values are converted according to the EDM type and
|
|
427
|
+
the ABAP type: dates, times, timestamps (`/Date(ms)/` in V2, ISO in V4 with the precision of
|
|
428
|
+
the property), UUIDs, exact decimals as text (`IEEE754Compatible=true` in V4), booleans,
|
|
429
|
+
binary; the trailing blanks of the `c` fields are not sent; the ETag (`@odata.etag`,
|
|
430
|
+
`__metadata.etag`) fills the `etag` component of the SCM2 classes;
|
|
431
|
+
- modifying requests: CSRF token requested with `HEAD` on the root of the service
|
|
432
|
+
(`X-CSRF-Token: Fetch`, can be disabled with `iv_do_fetch_csrf_token`), session cookies
|
|
433
|
+
kept, retry with a new token after `403` + `X-CSRF-Token: Required`, as SAP Gateway requires.
|
|
434
|
+
- **Implementation**: the ABAP keeps the state of the objects (request, headers, filters); the
|
|
435
|
+
kernel (`ZRAP_EML`) makes the network calls (`fetch` of Node.js, 60 s by default or
|
|
436
|
+
`i_timeout`), reads the `$metadata` (fast-xml-parser, already used by the compiler) and
|
|
437
|
+
converts the values.
|
|
438
|
+
- **Why**: calling an API from an action, a determination or a validation is a common need
|
|
439
|
+
(side-by-side extension, enrichment, check against another system); the developer keeps the
|
|
440
|
+
code they write on SAP. The SAP Gateway classes do not exist outside an SAP system: they are
|
|
441
|
+
reimplemented, without SAP code.
|
|
442
|
+
- **Tests**: `rap-variant-remote.test.ts` (REST API: destination with basic auth, headers,
|
|
443
|
+
status, missing destination, refused connection; OData V4 with an SCM2 class to the
|
|
444
|
+
application of the fixture served on a port: filter, `$select`, `$orderby`, `$top`, `$count`,
|
|
445
|
+
read by key; V4 writes to a simulated service: CSRF, cookies, ETags, token renewal, 409 error;
|
|
446
|
+
OData V2 with the abstract entity of an SCM: `d.results`, `__count`, `/Date()/`),
|
|
447
|
+
`http-client.test.ts`, `app-scaffold.test.ts`.
|
|
448
|
+
|
|
449
|
+
## D32 — Dependent ETag and ETag in a change set
|
|
450
|
+
|
|
451
|
+
- **Decision**: an `etag dependent by _Assoc` entity has the ETag of its master: the ETag is the
|
|
452
|
+
value of the ETag field of the master instance reached through the `by` associations (one or
|
|
453
|
+
several, up to an `etag master`). The service exposes it (`@odata.etag` of the instances,
|
|
454
|
+
`ETag` header, `Core.OptimisticConcurrency` with the path `_Travel/LocalLastChangedAt`) and
|
|
455
|
+
checks `If-Match` on the changes of the dependent entity (428 without the header, 412 when
|
|
456
|
+
stale).
|
|
457
|
+
- **The master changes with its dependents**: the SAP rule is that the ETag field is updated on
|
|
458
|
+
each change of the instance it covers; when a dependent instance is created, changed or
|
|
459
|
+
deleted, the runtime updates the ETag field of the master when it manages it (administrative
|
|
460
|
+
timestamp `localInstanceLastChangedAt` or `lastChangedAt`, master written by the runtime), once
|
|
461
|
+
per LUW, at save time. A master with unmanaged save or with another ETag field must be updated
|
|
462
|
+
by the application.
|
|
463
|
+
- **`If-Match` in a change set**: it is compared with the ETag of the instance at the beginning
|
|
464
|
+
of the LUW, the state the client read. The requests of a change set carry the ETags read before
|
|
465
|
+
it: an earlier change in the same change set (the previous request, or a determination that
|
|
466
|
+
recomputes the travel when a booking changes) does not make them stale. The changes of the
|
|
467
|
+
other clients are still detected: the LUW locks the instances it changes.
|
|
468
|
+
- **Why**: without it, the Object Page of a non-draft BO that saves a travel and its bookings in
|
|
469
|
+
one change set would fail with 412 as soon as a determination touches the travel; and a
|
|
470
|
+
dependent entity would have no concurrency protection.
|
|
471
|
+
- **Implementation**: OData model (`etagPath`), CSDL, read (scalar subqueries along the
|
|
472
|
+
navigations, in the lists, the single reads and `$expand`), write check
|
|
473
|
+
(`Transaction.etagValue`), update of the master (`refreshEtagMasters`, before the write).
|
|
474
|
+
Tests: `rap-variant-etag.test.ts`.
|
|
475
|
+
|
|
476
|
+
## D33 — PGlite and Keycloak proven end to end
|
|
477
|
+
|
|
478
|
+
- **PGlite** (`PGLITE_DIR`): the e2e test starts the generated application on its embedded
|
|
479
|
+
database, creates a travel, stops it and starts it again: the schema is applied again
|
|
480
|
+
(idempotent) and the data is there. The test revealed that the application did not start on
|
|
481
|
+
PGlite: the schema was sent as one query of several statements, which the extended protocol of
|
|
482
|
+
PGlite refuses (`cannot insert multiple commands into a prepared statement`). It is now split
|
|
483
|
+
into statements (`splitStatements`: literals, identifiers, `$f$…$f$` bodies and comments
|
|
484
|
+
respected) and applied in a transaction.
|
|
485
|
+
- **Keycloak**: `keycloak.test.ts` plays the browser against a real Keycloak with the generated
|
|
486
|
+
realm: redirect to Keycloak, submission of the sign-in form of `alice`, return to
|
|
487
|
+
`/auth/callback` (authorization code with PKCE), session on the UI and on the OData service
|
|
488
|
+
(CSRF token, creation attributed to `ALICE`), sign-out to the end-session endpoint of Keycloak;
|
|
489
|
+
then a bearer token issued by Keycloak (public client created through the admin API, password
|
|
490
|
+
grant) verified by the JWKS, and a forged token refused. Run against the Keycloak 26.8.2
|
|
491
|
+
distribution (Maven Central, Java 21) in the development environment, and in CI against the
|
|
492
|
+
image of the generated `docker-compose.yml` (`--profile oidc`).
|
|
493
|
+
- **CI**: the health check of the PostgreSQL service uses `pg_isready -U postgres`.
|
|
494
|
+
|
|
495
|
+
## D34 — Authorizations: authorization objects, OIDC roles, AUTHORITY-CHECK
|
|
496
|
+
|
|
497
|
+
- **Problem**: the ABAP environment splits the authorizations between the IAM (IAM apps,
|
|
498
|
+
business catalogs, business roles with their restrictions, assigned to business users) and the
|
|
499
|
+
code (`AUTHORITY-CHECK` in the handlers, DCL access controls). Outside SAP, there is no IAM;
|
|
500
|
+
OIDC authenticates and carries roles, but does not say which values of authorization objects a
|
|
501
|
+
role grants. The transpiler translates `AUTHORITY-CHECK` into a call to a kernel class
|
|
502
|
+
`KERNEL_AUTHORITY_CHECK` the application did not provide: the statement compiled but failed at
|
|
503
|
+
runtime (500).
|
|
504
|
+
- **Decision**:
|
|
505
|
+
- the authorization objects come from the abapGit sources (SUSO: fields `FIEL1…`, activities
|
|
506
|
+
`TACTZ`), or from the `objects` section of the roles file;
|
|
507
|
+
- the roles are in `rapt/authorizations.json`, at the root of the abapGit project, versioned
|
|
508
|
+
with the sources (outside the `src/` folder, abapGit ignores it): name of the role of the
|
|
509
|
+
IdP → authorizations, instances of objects with their values, like a business role and its
|
|
510
|
+
restrictions;
|
|
511
|
+
- values in the PFCG way: value, pattern `07*`, `*`, range `{ "from", "to" }`, and values from
|
|
512
|
+
a claim of the token (`{claim:agency}`: the agency of the user, attribute of the IdP), so as
|
|
513
|
+
not to create one role per value;
|
|
514
|
+
- `AUTHORITY-CHECK` is evaluated by the kernel on the roles of the user of the request:
|
|
515
|
+
`sy-subrc` 0 when an authorization of the object accepts all the checked fields (`DUMMY` is
|
|
516
|
+
not checked), 4 otherwise, 12 without an authorization for the object, 40 for `FOR USER` of
|
|
517
|
+
another user; the ABAP code stays the one of SAP;
|
|
518
|
+
- the IdP (Keycloak, Entra ID, SAP Cloud Identity Services…) assigns the roles to the users
|
|
519
|
+
(`OIDC_ROLES_CLAIM`) and carries the attributes; the application keeps the claims read by the
|
|
520
|
+
roles (session included); `OIDC_REQUIRED_ROLE` stays the right to access the application;
|
|
521
|
+
- in development (`AUTH_MODE=none`), the user has all the authorizations (like SAP_ALL in a
|
|
522
|
+
development system), unless `DEV_ROLES` (and `DEV_ATTRIBUTES`) are given;
|
|
523
|
+
`AUTHORIZATIONS_FILE` replaces the roles on deployment;
|
|
524
|
+
- the compiler checks the roles against the objects (AUTH001…AUTH004) and the
|
|
525
|
+
`AUTHORITY-CHECK`s of the code (ABP010, ABP011); the generated Keycloak realm contains the
|
|
526
|
+
roles (given to `alice`), a user attribute declared in the profile and a mapper to the token
|
|
527
|
+
for each claim read.
|
|
528
|
+
- **Why**: the same behavior pool runs on SAP and here; the definition of the roles stays
|
|
529
|
+
reviewed and versioned like the IAM objects; the administration of the users stays in the IdP.
|
|
530
|
+
- **Fixture**: the reference BO checks the `ZAO_TRAVL` object (activity, agency) in its global
|
|
531
|
+
and instance authorizations, like the /DMO/ reference scenario of SAP; roles `travel_manager`,
|
|
532
|
+
`travel_agent` (agency from the `agency` claim), `travel_viewer`.
|
|
533
|
+
- **Tests**: `rap-authorizations.test.ts` (locally signed tokens), `authorizations.test.ts`
|
|
534
|
+
(semantics of `sy-subrc`), `authorizations.test.ts` of the compiler (diagnostics),
|
|
535
|
+
`keycloak.test.ts` (an agent created in Keycloak with their agency attribute).
|
|
536
|
+
|
|
537
|
+
## D35 — Access controls (DCL) on the reads
|
|
538
|
+
|
|
539
|
+
- **Decision**: the CDS roles of the abapGit sources (DCLS objects: `define role R { grant select
|
|
540
|
+
on E where … ; }`) restrict the rows the OData service returns to the user of the request, in
|
|
541
|
+
the lists, `$count`, the single reads, `$expand` and the navigations. A row is granted when a
|
|
542
|
+
condition of a role of the entity is true; an entity without a role is fully readable (as in
|
|
543
|
+
ABAP CDS).
|
|
544
|
+
- **Evaluated conditions**: `( E1, E2 ) = aspect pfcg_auth( OBJ, F1, F2, ACTVT = '03' )` on the
|
|
545
|
+
authorizations of the user (D34: values, patterns, ranges, claims; no authorization → no row),
|
|
546
|
+
`E ?= aspect user` / `= aspect user` (ABAP name of the user), comparisons with literals (ABAP
|
|
547
|
+
format: dates `YYYYMMDD`), `like`, `is [not] null`, `and`, `or`, `not`, parentheses, paths of a
|
|
548
|
+
to-one association (`_Travel.AgencyID`, read by a subquery on the target view) and
|
|
549
|
+
`inheriting conditions from entity B`: a projection takes the conditions of its base view, its
|
|
550
|
+
elements renamed as it exposes them.
|
|
551
|
+
- **Security**: what cannot be evaluated (other aspects, longer paths, element not exposed by the
|
|
552
|
+
projection) grants nothing (deny by default), with a diagnostic (DCL002, DCL003). A read outside
|
|
553
|
+
a request grants nothing. The development user without `DEV_ROLES` is not restricted.
|
|
554
|
+
- **What is not filtered**: the EML and the ABAP SQL of the behavior pool (like
|
|
555
|
+
`WITH PRIVILEGED ACCESS`); the changes go through the instance authorization, as in RAP.
|
|
556
|
+
- **Implementation**: DCL parser (`dcl/parser.ts`), resolution for the exposed entities
|
|
557
|
+
(`ir/access-controls.ts`, conditions in `model.accessControls`), SQL condition computed per
|
|
558
|
+
request (`odata/access.ts`, user carried by an `AsyncLocalStorage`) and added to the `WHERE` of
|
|
559
|
+
the read engine.
|
|
560
|
+
- **Fixture**: roles `ZRT_R_TRAVEL` (agency through
|
|
561
|
+
`pfcg_auth( ZAO_TRAVL, ZAGENCY, ACTVT = '03' )`), `ZRT_C_TRAVEL` (inherits from the base view),
|
|
562
|
+
`ZRT_C_BOOKING` (agency of the travel, `_Travel.AgencyID`).
|
|
563
|
+
|
|
564
|
+
## D36 — Authorizations in the consumer hints (UI)
|
|
565
|
+
|
|
566
|
+
- **Decision**: like RAP, which presents the feature control and the authorization check as
|
|
567
|
+
consumer hints, the entities whose BO has authorization handlers (master with a global or
|
|
568
|
+
instance handler, or entity dependent on such a master) expose `__EntityControl` (`Updatable`,
|
|
569
|
+
`Deletable`) and `__OperationControl` (instance actions, create by association); their values
|
|
570
|
+
combine, for the user of the request, the feature controls and the global and instance
|
|
571
|
+
authorizations. The `$metadata` announces them (`UpdateRestrictions`, `DeleteRestrictions`,
|
|
572
|
+
`NavigationRestrictions` per path, `Core.OperationAvailable`), usable by a Fiori elements
|
|
573
|
+
application as well.
|
|
574
|
+
- **Creation**: it depends on no instance; the service answers `GET <root>$permissions` (creations
|
|
575
|
+
allowed per entity set: global authorization and global feature control) and the generated UI
|
|
576
|
+
hides Create when it is refused.
|
|
577
|
+
- **Create by association**: for the master entity, it has its own indicator
|
|
578
|
+
(`%assoc-_Booking`), as on SAP; for a dependent entity, the update authorization of the master
|
|
579
|
+
applies. The fixture grants the creation of bookings with the update of the travel.
|
|
580
|
+
- **Tests**: `rap-authorizations.test.ts` (checks per profile, `$permissions`, creation of
|
|
581
|
+
bookings), `ui-authorizations.test.ts` (browser: a viewer does not see Create, Edit, Delete and
|
|
582
|
+
the action are disabled).
|
|
583
|
+
|
|
584
|
+
## D37 — Launchpad of the applications (OpenUI5)
|
|
585
|
+
|
|
586
|
+
- **Decision**: `rapt launchpad <launchpad.json> --out <folder>` generates a launchpad, the
|
|
587
|
+
equivalent of the SAP Fiori launchpad built with OpenUI5. The SAP shell (`sap.ushell`) only
|
|
588
|
+
exists in SAPUI5, under an SAP license, and the rule of the project excludes it. The launchpad
|
|
589
|
+
is made of a `sap.f.ShellBar` (home, tile search, user menu with sign-out) and of groups of
|
|
590
|
+
`sap.m.GenericTile`. A dynamic tile (`NumericContent`) shows the `$count` of an entity set,
|
|
591
|
+
with an optional filter. Navigation is by intent (`/#Travel-manage`, `/go/Travel-manage?…`).
|
|
592
|
+
- **Content**: `launchpad.json` is versioned with the sources, like the content of the launchpad
|
|
593
|
+
(catalogs, groups, tiles, target mappings). It lists the applications (folder generated by
|
|
594
|
+
`rapt build`, internal URL) and the groups of tiles. A tile is assigned to roles of the IdP, the
|
|
595
|
+
equivalent of the business catalog of a business role; without a role, every user sees it. The
|
|
596
|
+
compiler resolves the service paths and checks the entity sets of the counters, the intents and
|
|
597
|
+
the roles (`LPD001` to `LPD007`); it takes the roles and the claims of the applications for the
|
|
598
|
+
realm and the session.
|
|
599
|
+
- **Architecture, a single origin**: the SAP front-end server serves the launchpad, the
|
|
600
|
+
applications and their services under one domain. Here, the server of the launchpad mounts the
|
|
601
|
+
UI of each application under `/apps/<id>/` and relays its services at their own paths
|
|
602
|
+
(`/sap/opu/odata4/…`, unique per service); the redirects of the applications are rewritten
|
|
603
|
+
under their mount point. The browser sees a single origin: no CORS, the UI reads the counters
|
|
604
|
+
directly, the cookies are shared.
|
|
605
|
+
- **Single sign-on**: the launchpad opens the OIDC session (code + PKCE). The applications behind
|
|
606
|
+
it have the same `SESSION_SECRET` (and the client and `PUBLIC_URL` of the launchpad) and read
|
|
607
|
+
this session: user, roles, claims read by their authorizations. Each application keeps its
|
|
608
|
+
authorizations (D34), its access controls (D35) and its required role, now checked on each
|
|
609
|
+
request: a session opened by the launchpad for a user without this role receives a 403. The
|
|
610
|
+
bearer tokens of the API clients are relayed as is.
|
|
611
|
+
- **Sign-out**: the ID token of the sign-in is kept in a separate HttpOnly cookie (`rapt_idt`)
|
|
612
|
+
and passed as `id_token_hint` to the end-session endpoint. The provider then ends its session
|
|
613
|
+
without a confirmation page, even with an expired token, as OIDC RP-Initiated Logout provides
|
|
614
|
+
(checked with Keycloak). This also applies to a standalone application.
|
|
615
|
+
- **Why not the relayed token (BFF)**: relaying the access token of the user would have required
|
|
616
|
+
keeping the tokens on the server and refreshing them (5 minutes by default with Keycloak). The
|
|
617
|
+
shared signed session stays stateless, like the one of the applications (D28).
|
|
618
|
+
- **In the applications**: the generated UI, served behind a launchpad (`/launchpad/config`
|
|
619
|
+
answers), shows the shell bar of the launchpad above its pages (home, user, sign-out). Alone,
|
|
620
|
+
it is unchanged. Each application stays deployable on its own.
|
|
621
|
+
- **Operations**: the generated folder contains `server.mjs`, a Dockerfile and a
|
|
622
|
+
`docker-compose.yml`: the launchpad, each application built from its folder with its PostgreSQL
|
|
623
|
+
database, and Keycloak in the `oidc` profile (client `rapt-launchpad`). Plus `.env.example`
|
|
624
|
+
(`APP_<ID>_URL`) and a README.
|
|
625
|
+
- **Tests**: `runtime/test/launchpad.test.ts` covers the tiles per role and the sign-in against a
|
|
626
|
+
test OIDC provider, then the reading of the session by the application. It also checks,
|
|
627
|
+
through the gateway, the DCL, the authorizations, the CSRF and the bearer tokens, as well as
|
|
628
|
+
the intents, the rewriting of the redirects, the 502 and the required role.
|
|
629
|
+
`runtime/test/launchpad-ui.test.ts` tests in the browser the tiles of a viewer, the counter,
|
|
630
|
+
the search, the application opened with the shell bar, the way back and the intent.
|
|
631
|
+
`compiler/test/launchpad.test.ts` covers the diagnostics, the generated files and the CLI.
|
|
632
|
+
|
|
633
|
+
## D38 — Development loop (`rapt dev`)
|
|
634
|
+
|
|
635
|
+
- **Context**: building, installing and starting the generated application takes four commands
|
|
636
|
+
(`rapt build`, `npm install`, a PostgreSQL database, `npm start`) at each change of a source.
|
|
637
|
+
The standalone application is the target: the editing loop must be immediate.
|
|
638
|
+
- **Decision**: `rapt dev <src>` builds the application without its scaffold (no `vendor/`, no
|
|
639
|
+
`package.json`, no Docker) into `<src>/.rapt/dev`, starts it, watches the sources and starts
|
|
640
|
+
again on each change (grouped over 150 ms, one build at a time).
|
|
641
|
+
- **Dependencies**: the `node_modules` of the build is a link (a junction on Windows) to the one
|
|
642
|
+
of the `@open-rap/runtime` package, which contains the abaplint runtime, PGlite and
|
|
643
|
+
OpenUI5. The transpiled ABAP module and the runtime thus load the same `@abaplint/runtime`. No
|
|
644
|
+
npm installation.
|
|
645
|
+
- **Child process**: the server runs in a separate process (`dev-server.mjs`), stopped by an
|
|
646
|
+
IPC message (PGlite closes its files) then started again. The transpiled ABAP code sets a
|
|
647
|
+
global state (the `abap` object) and ES modules cannot be unloaded: a restart in the same
|
|
648
|
+
process would keep the old behavior pool.
|
|
649
|
+
- **Errors**: a build in error does not touch the running application; the diagnostics keep the
|
|
650
|
+
format of `rapt check`.
|
|
651
|
+
- **Database**: PGlite in `.rapt/dev/data`, kept between builds. The applied schema is
|
|
652
|
+
remembered; when it changes, the database is deleted and created again (development data, no
|
|
653
|
+
migration). With `DATABASE_URL`, the given database is used as is.
|
|
654
|
+
- **Watching**: one `fs.watch` per folder, not the recursive mode. On Linux, the recursive mode
|
|
655
|
+
of Node.js loses the files replaced by a rename, which most editors do on save. Folders
|
|
656
|
+
starting with a dot and `node_modules` are ignored, except `.abapgit.xml`.
|
|
657
|
+
- **Defaults**: `AUTH_MODE=none`, `HOST=127.0.0.1`, `LOG_LEVEL=warn`; each environment
|
|
658
|
+
variable overrides them.
|
|
659
|
+
- **Tests**: `compiler/test/dev.test.ts` covers the build, the behavior pool run, the data kept,
|
|
660
|
+
the build in error, the reset on a schema change and the watching of a file replaced by a
|
|
661
|
+
rename.
|
|
662
|
+
|
|
663
|
+
## D39 — JSON output of the CLI and VS Code extension
|
|
664
|
+
|
|
665
|
+
- **Context**: everything must be possible on the command line, or visually in VS Code. Two
|
|
666
|
+
implementations of the compiler would diverge.
|
|
667
|
+
- **Decision**: the CLI stays the only engine; the VS Code extension
|
|
668
|
+
([packages/vscode](../packages/vscode)) calls it and shows its results.
|
|
669
|
+
- **`--json`**: `rapt check` and `rapt build` write a JSON object (`command`, `ok`, `root`,
|
|
670
|
+
`counts`, `diagnostics` with, in addition to `file`, the absolute path `path`); `rapt build`
|
|
671
|
+
adds `out` and `files`. `rapt dev` writes one JSON event per line (`building`, `changed`,
|
|
672
|
+
`diagnostics`, `ready`, `failed`, `database-reset`, `server-failed`, `server-stopped`,
|
|
673
|
+
`error`, `log` for the logs of the server). The text of `rapt dev` is derived from the same
|
|
674
|
+
events (`formatDevEvent`).
|
|
675
|
+
- **Extension**: diagnostics in the Problems tab (on each save, or from `rapt dev` when it runs
|
|
676
|
+
on the project), commands *Check the BO*, *Build the application*, *Start / Stop rapt dev*,
|
|
677
|
+
*Open the application*, state in the status bar, menus of the explorer and of the title bar.
|
|
678
|
+
A project is the folder of `.abapgit.xml` (otherwise, the parent of `src`).
|
|
679
|
+
- **Standalone extension**: the `.vsix` (~31 MB, ~9,100 files) contains `rapt/`: the packages
|
|
680
|
+
of the repository (`pnpm pack`) installed by npm, flat, with their production dependencies
|
|
681
|
+
and what `rapt dev` serves (OpenUI5, `less-openui5`, PGlite, at the versions of the runtime),
|
|
682
|
+
without source maps, types, documentation, tests of the packages, OpenUI5 design time nor
|
|
683
|
+
PGlite extensions (`scripts/stage-rapt.mjs`). A download on first start would have made the
|
|
684
|
+
`.vsix` lighter, but required access to the npm registry (often filtered in companies) and
|
|
685
|
+
cache code; the `.vsix` goes through VS Code, offline once installed, and rapt always has the
|
|
686
|
+
version of the extension.
|
|
687
|
+
- **Node.js of VS Code**: rapt runs on the executable of VS Code with `ELECTRON_RUN_AS_NODE=1`
|
|
688
|
+
(Node.js 22 since VS Code 1.101, minimum version of the extension); the server of `rapt dev`
|
|
689
|
+
inherits the variable. Checked with Electron 37: `check`, `dev`, PGlite, behavior pool, stop
|
|
690
|
+
of the server when `rapt dev` is killed.
|
|
691
|
+
- **`rapt dev` installed flat**: the `node_modules` linked in `.rapt/dev` is the first folder,
|
|
692
|
+
from the runtime to the root, that contains `@abaplint/runtime` (the one of the runtime in
|
|
693
|
+
the pnpm repository, the one of `rapt/` in the extension).
|
|
694
|
+
- **CLI found**: `rapt.cliPath` setting, otherwise the bundled rapt, otherwise an Open RAP Runtime
|
|
695
|
+
clone or a `node_modules` of the workspace, otherwise `rapt` from the PATH; Node.js from
|
|
696
|
+
`rapt.nodePath`, otherwise the one of VS Code.
|
|
697
|
+
- **CI**: the `test` job builds the `.vsix`, checks the fixture with the bundled rapt and
|
|
698
|
+
publishes the `.vsix` as an artifact (`open-rap-runtime-vscode`).
|
|
699
|
+
- **Stopping `rapt dev`**: SIGINT. On Windows, where this signal terminates the process, the
|
|
700
|
+
server stops when its IPC channel with `rapt dev` closes (D38).
|
|
701
|
+
- **Build**: ESM sources checked by `tsc`, delivered as one CommonJS file by esbuild
|
|
702
|
+
(`dist/extension.cjs`, `vscode` external), without a runtime dependency; `.vsix` by
|
|
703
|
+
`vsce package`.
|
|
704
|
+
- **Tests**: `vscode/test/rapt.test.ts` covers the search of the project and of the CLI, the
|
|
705
|
+
reading of the JSON lines, `rapt check --json` and `rapt dev --json` really started.
|
|
706
|
+
`compiler/test/cli.test.ts` and `dev.test.ts` cover the JSON output and the events. The
|
|
707
|
+
interface of the extension (commands, status bar) is not tested in VS Code.
|
|
708
|
+
|
|
709
|
+
## D40 — ABAP Cloud utility classes in the behavior pool
|
|
710
|
+
|
|
711
|
+
- **Context**: after the HTTP client (D31), the behavior pools need the other commonly used
|
|
712
|
+
released classes: JSON for the APIs, encodings, numbering, dates, application log, hashing,
|
|
713
|
+
random numbers, regular expressions, RTTS. Without them, ordinary ABAP Cloud code does not pass
|
|
714
|
+
the check.
|
|
715
|
+
- **Decision**: a clean-room implementation in ABAP of these classes
|
|
716
|
+
(`gen/abap/framework-utils.ts`), written from the public signatures documented by SAP, with
|
|
717
|
+
methods of the `ZRAP_EML` kernel for what ABAP cannot do alone (`runtime/src/abap/utils.ts`).
|
|
718
|
+
Guide: [UTILITIES.md](UTILITIES.md).
|
|
719
|
+
- **Closure**: only the objects named by the sources of the project, then those they name, are
|
|
720
|
+
given to the transpiler. A project that uses none of them keeps the same build and the same
|
|
721
|
+
check time; an object of the project with the same name wins.
|
|
722
|
+
- **The kernel**: JSON (parser that keeps the numbers as written, traversal of the data of
|
|
723
|
+
`@abaplint/runtime` by type, names of the members according to the mode), code pages, base64,
|
|
724
|
+
gzip (zlib), hashing and HMAC (crypto), random seed, regular expressions (RegExp with `d` for
|
|
725
|
+
the submatches, extended mode and POSIX classes translated), calendar and timestamps in
|
|
726
|
+
tenths of a microsecond (BigInt), description of a data object for the RTTS. The RTTS are
|
|
727
|
+
built in ABAP, one level at a time: `ASSIGN COMPONENT` for the structures,
|
|
728
|
+
`CREATE DATA … LIKE LINE OF` for the tables.
|
|
729
|
+
- **Numbering**: intervals of `rapt/number-ranges.json` (diagnostic NR001), inserted by
|
|
730
|
+
`db/schema.sql` without touching the current number; table `rapt_number_range`, written in the
|
|
731
|
+
LUW (the LUWs are serialized: no conflict). An interval that is not configured starts at 1, to
|
|
732
|
+
develop without configuration.
|
|
733
|
+
- **Application log**: tables `rapt_application_log` and `rapt_application_log_item`, in the
|
|
734
|
+
LUW; read by filter.
|
|
735
|
+
- **Tables**: generated only when the code uses `cl_numberrange_runtime` or `cl_bali_*`
|
|
736
|
+
(`model.services`), so as not to change the schema of the other applications.
|
|
737
|
+
- **Global types**: `string_table`, `xstring_table`, `match_result(_tab)`,
|
|
738
|
+
`submatch_result(_tab)` as DDIC objects (TABL, TTYP), and an `ABAP` type pool
|
|
739
|
+
(`abap_compdescr_tab`, `abap_component_tab`…), which the analysis of abaplint resolves.
|
|
740
|
+
- **Class constructors**: they run when the modules are loaded, before the kernel; those of the
|
|
741
|
+
utilities therefore do not call `ZRAP_EML` (`cl_abap_char_utilities` writes its control
|
|
742
|
+
characters as literals).
|
|
743
|
+
- **Tests**: `runtime/test/utilities.test.ts` runs a self-test written in ABAP in a behavior pool
|
|
744
|
+
(`test/utilities/selftest.abap`: more than seventy checks on all the classes, including the
|
|
745
|
+
reference values of SHA-256, MD5 and HMAC), draws numbers until an interval is exhausted and
|
|
746
|
+
writes then reads a log. `compiler/test/utilities.test.ts` covers the closure, the check of all
|
|
747
|
+
the classes and `rapt/number-ranges.json`.
|
|
748
|
+
|
|
749
|
+
## D41 — Background processing (bgPF) and e-mail
|
|
750
|
+
|
|
751
|
+
- **Context**: `cl_bgmc_process_factory` and `cl_bcs_mail_message` are commonly used in RAP BOs
|
|
752
|
+
(deferred work after the save, notifications). Both rely on the LUW: the process only starts
|
|
753
|
+
and the mail only leaves after the `COMMIT WORK`.
|
|
754
|
+
- **Decision**: the runtime keeps, for each LUW, the work to do after its commit
|
|
755
|
+
(`LuwContext.afterCommit`). A rolled-back LUW forgets it; a committed LUW starts it after the
|
|
756
|
+
response (`setImmediate`), each item in a LUW of its own, serialized with the others.
|
|
757
|
+
- **bgPF**: `save_for_execution` writes the process (`rapt_background_process`, `NEW`) in the
|
|
758
|
+
LUW and keeps the reference of the ABAP operation. The execution calls `execute` in a LUW
|
|
759
|
+
marked "background": controlled (`if_bgmc_op_single`), the EML is saved at the end and ABAP
|
|
760
|
+
SQL writes after `cl_abap_tx=>save( )`; uncontrolled, ABAP SQL always writes,
|
|
761
|
+
`COMMIT ENTITIES` starts the save sequence (once) and `ROLLBACK ENTITIES` rolls back the LUW.
|
|
762
|
+
Outside the background, `COMMIT ENTITIES` stays refused in a behavior pool. The final state
|
|
763
|
+
(`SUCCESSFUL`, `ERRONEOUS` with the text of the exception) is written in a separate LUW, to
|
|
764
|
+
survive the rollback of the one of the operation.
|
|
765
|
+
- **Why in memory**: SAP serializes the operation object to run it later, elsewhere.
|
|
766
|
+
Serializing an object of the abaplint runtime (references, nested objects) would require a
|
|
767
|
+
general mechanism; the application being a single process, the reference is enough. Limit: a
|
|
768
|
+
process not run at shutdown is not resumed.
|
|
769
|
+
- **E-mail**: `send( )` composes the MIME message with nodemailer (MIT-0), keeps it in
|
|
770
|
+
`rapt_mail` (`WAITING`), then sends it after the commit through the server of
|
|
771
|
+
`MAIL_SMTP_URL` (`SENT` / `ERROR`). Without a server, the mail stays in the table (`STORED`)
|
|
772
|
+
with its complete message: development needs no server. `MAIL_FROM` gives the default sender;
|
|
773
|
+
both variables are in the `.env.example` of the applications that send mails.
|
|
774
|
+
- **Tables**: generated only if the code uses `cl_bgmc_process_factory` or
|
|
775
|
+
`cl_bcs_mail_message` (`model.services`, D40).
|
|
776
|
+
- **Tests**: `runtime/test/background.test.ts`: an action starts three processes (controlled
|
|
777
|
+
with EML, uncontrolled with `COMMIT ENTITIES`, failing) and checks their states and effects;
|
|
778
|
+
another sends a mail (HTML, copy, text and binary attachments), kept without a server, sent to
|
|
779
|
+
a test SMTP server with `MAIL_SMTP_URL`, and nothing is sent when the LUW is rolled back.
|
|
780
|
+
|
|
781
|
+
## D42 — Writing only ABAP: tables as source, `rapt abapgit`, `rapt new`, initial data
|
|
782
|
+
|
|
783
|
+
- **Context**: an abapGit export requires, for each object, an XML metadata file, and the tables
|
|
784
|
+
only exist as XML (DD02V, DD09L, DD03P). Writing these files by hand discourages a developer
|
|
785
|
+
who wants to write their BO in VS Code without an SAP system.
|
|
786
|
+
- **Decision**:
|
|
787
|
+
- **Tables as source**: `name.tabl.astabl`, with the syntax of the table editor of the ABAP
|
|
788
|
+
Development Tools (`define table … { key client : abap.clnt not null; … }`, annotations
|
|
789
|
+
`@EndUserText.label`, `@AbapCatalog.*`, `@Semantics.amount.currencyCode` /
|
|
790
|
+
`@Semantics.quantity.unitOfMeasure` for the references, `include`, `define structure`).
|
|
791
|
+
`loadProject` derives the abapGit XML from it in memory: the rest of the compiler only reads
|
|
792
|
+
the XML, and the diagnostics of the tables carry the line of the field in the source. The
|
|
793
|
+
source wins over an XML next to it. Foreign keys (`with foreign key`) are reported (TABL002)
|
|
794
|
+
and not carried over.
|
|
795
|
+
- **`rapt abapgit <src>`** writes what is missing for abapGit to import the repository into an
|
|
796
|
+
SAP system: `.abapgit.xml` (which ignores `*.astabl`, unknown to abapGit),
|
|
797
|
+
`package.devc.xml`, the XML of the tables written as source (regenerated each time), and the
|
|
798
|
+
missing XML of the CDS entities (source type derived: view, projection, abstract entity,
|
|
799
|
+
custom entity…), metadata extensions, access controls, BDEF, SRVD, classes and interfaces
|
|
800
|
+
(description from `@EndUserText.label` or from the ABAP Doc short text). An existing XML is
|
|
801
|
+
never changed. `--check` only reports what is missing (exit code 1), for the CI.
|
|
802
|
+
- **`rapt new <src> <Entity> [--prefix ZRT] [--description …]`** writes a complete managed BO,
|
|
803
|
+
activatable on SAP: table as source, root view and projection, metadata extension (List
|
|
804
|
+
Report and Object Page), BDEF and projection, behavior pool (global authorization, one
|
|
805
|
+
determination), SRVD and SRVB OData V4, `rapt/seed.json`, `.gitignore` (`.rapt/`,
|
|
806
|
+
`node_modules/`), then the XML files of `rapt abapgit`. Existing files are never overwritten.
|
|
807
|
+
Command *RAP: New business object* in the extension.
|
|
808
|
+
- **Initial data**: `rapt/seed.json` (table → rows, field names of the dictionary, in lower
|
|
809
|
+
case, without the client) and `rapt/seed.sql`, checked at compile time (SEED001, SEED002) and
|
|
810
|
+
copied into `db/` of the application. With `SEED=auto` (default of `rapt dev`), the
|
|
811
|
+
application loads them on its first start, when all its tables are empty: a missing UUID key
|
|
812
|
+
is drawn at random, a NUMC receives its leading zeros. Without `SEED=auto` (in production by
|
|
813
|
+
default), nothing is loaded.
|
|
814
|
+
- **Why not a "source-less" XML**: abapGit can only serialize tables as XML; the `.astabl` source
|
|
815
|
+
is what an ADT developer already reads and writes, and the conversion to XML is deterministic.
|
|
816
|
+
Keeping both files in the repository (the XML regenerated by `rapt abapgit`) allows the import
|
|
817
|
+
into SAP without a tool.
|
|
818
|
+
- **Tests**: `compiler/test/authoring.test.ts` (the source of `zrt_booking` gives the same XML
|
|
819
|
+
and the same model as the fixture; positioned errors; `rapt abapgit` recreates the XML files
|
|
820
|
+
removed from the fixture identically; `rapt new` produces a BO that compiles without a
|
|
821
|
+
diagnostic) and `runtime/test/seed.test.ts`.
|
|
822
|
+
|
|
823
|
+
## D43 — Custom entities (unmanaged query) and managed BO on a custom entity
|
|
824
|
+
|
|
825
|
+
- **Context**: the data of a BO sometimes comes from APIs, not tables. ABAP Cloud exposes it with
|
|
826
|
+
a custom entity (`define custom entity`) whose query class
|
|
827
|
+
(`@ObjectModel.query.implementedBy: 'ABAP:<class>'`, interface `if_rap_query_provider`)
|
|
828
|
+
provides the rows.
|
|
829
|
+
- **Decision**:
|
|
830
|
+
- **Custom entity** (activatable on SAP): the model keeps it without an SQL view, with its
|
|
831
|
+
query class, its typed elements and its associations (`_X : association … to … on …`). The
|
|
832
|
+
OData reads (collection, instance, navigation, `$count`, one-level `$expand`, to or from a
|
|
833
|
+
custom entity) call `select` with the request of ABAP Cloud: filter as an ABAP SQL string
|
|
834
|
+
(`get_as_sql_string`) and as ranges (`get_as_ranges`, `cx_rap_query_filter_no_range` when the
|
|
835
|
+
filter does not fit), sorting, paging (`get_paging`), requested elements,
|
|
836
|
+
`is_total_numb_of_rec_requested`, `is_data_requested`, search. `set_data` and
|
|
837
|
+
`set_total_number_of_records` give the result back to the runtime (kernel
|
|
838
|
+
`zrap_eml=>query_data`, `query_total`). As in SAP, the class applies the filter, the sorting
|
|
839
|
+
and the paging itself; the runtime only guarantees the keys and the navigation conditions
|
|
840
|
+
(extra rows dropped) and cuts the page at `$top`. The interfaces and classes of the query are
|
|
841
|
+
written from their public signatures (D24).
|
|
842
|
+
- **Managed BO with unmanaged save on a custom entity** (specific to Open RAP Runtime, warning
|
|
843
|
+
BDEF027): the RAP runtime reads the instances (READ ENTITIES, existence check, ETag,
|
|
844
|
+
associations) through the query class, with a filter on the keys, in the current LUW;
|
|
845
|
+
`save_modified` writes the changes to the API. No database lock: the LUWs of the application
|
|
846
|
+
are serialized. On SAP, the same need is handled by an unmanaged BO.
|
|
847
|
+
- **Why**: it is the ABAP Cloud way to read an API; the managed BO on the custom entity avoids
|
|
848
|
+
writing by hand the transactional buffer of an unmanaged BO, at the price of an accepted and
|
|
849
|
+
reported gap.
|
|
850
|
+
- **Tests**: fixture `fixtures/custom-entity` (products read from an "API" simulated by a table,
|
|
851
|
+
reviews as constants); `runtime/test/custom-entity.test.ts` (ranges, sorting, paging, count,
|
|
852
|
+
navigation, `$expand`, refused filter, creation, duplicate, validation, change, deletion) and
|
|
853
|
+
`compiler/test/custom-entity.test.ts`.
|
|
854
|
+
|
|
855
|
+
## D44 — SAP standard views (I_Currency…) and value helps
|
|
856
|
+
|
|
857
|
+
- **Context**: on SAP (ABAP Cloud as well as S/4HANA), the released CDS views `I_Currency`,
|
|
858
|
+
`I_UnitOfMeasure`, `I_Country`, `I_Language` and their text views exist in every system, with
|
|
859
|
+
content delivered and maintained by SAP (ISO currencies, units, countries, languages). A RAP BO
|
|
860
|
+
uses them for its value helps (`@Consumption.valueHelpDefinition`) and its associations.
|
|
861
|
+
Without them, a source activatable on SAP failed here (CDS005) and the value help was ignored.
|
|
862
|
+
- **Decision**:
|
|
863
|
+
- **Standard views**: a source of the project that names one of these views without defining
|
|
864
|
+
it receives it, with its text view (`_Text`, category `#TEXT`) and its tables
|
|
865
|
+
(`rapt_currency`, `rapt_currencyt`, `rapt_uom`, `rapt_uomt`, `rapt_country`,
|
|
866
|
+
`rapt_countryt`, `rapt_language`, `rapt_languaget`). The elements have the SAP names; the
|
|
867
|
+
tables are specific to Open RAP Runtime (ABAP Cloud code does not read the SAP tables). A view
|
|
868
|
+
defined by the project wins. The files are virtual (`<standard>/…`): `rapt abapgit` never
|
|
869
|
+
writes them.
|
|
870
|
+
- **Reference data**: ISO 4217 currencies (with their decimals), ISO 3166 countries
|
|
871
|
+
(three-letter code, currency, EU membership), common units (SAP code, ISO code, dimension),
|
|
872
|
+
languages (one-character SAP key, ISO code); texts in English. `db/schema.sql` inserts them
|
|
873
|
+
(`ON CONFLICT DO NOTHING`): a row added or changed by the application stays. The standard
|
|
874
|
+
tables do not count for "all the tables are empty" of the initial data (D42).
|
|
875
|
+
- **Value helps**: as in RAP, the entity of a value help and its text view are exposed
|
|
876
|
+
implicitly by the service, read-only, without a page in the UI. `$metadata` carries
|
|
877
|
+
`Common.ValueList` (`CollectionPath`, `InOut` parameter, `DisplayOnly` columns). The UI opens
|
|
878
|
+
a selection dialog (search on the text columns and on the descriptions) from the fields of the
|
|
879
|
+
Object Page and the filters of the List Report.
|
|
880
|
+
- **Answer to the question "must the currencies be maintained as on BTP?"**: on BTP, the content
|
|
881
|
+
of these views is delivered by SAP and is not to be entered; here it is delivered with
|
|
882
|
+
Open RAP Runtime the same way. The other released views useful next: `I_Region`,
|
|
883
|
+
`I_CalendarDate` (calendar), `I_Timezone`, `I_BusinessUserBasic` (users, to link to the IdP),
|
|
884
|
+
`I_ExchangeRate` (exchange rates, to link to a source of rates).
|
|
885
|
+
- **Views added afterwards**: `I_Region` and `I_RegionText` (regions of the United States,
|
|
886
|
+
Canada, Germany and Australia), `I_CalendarDate` (calendar from 1990 to 2060 computed by
|
|
887
|
+
`generate_series`: year, quarter, month, ISO week, day of the week), `I_TimeZone` and
|
|
888
|
+
`I_TimeZoneText` (common IANA time zones), `I_BusinessUserBasic` (a user of the IdP is recorded
|
|
889
|
+
in it on each authenticated request: identifier, name, e-mail) and `I_ExchangeRate` (empty
|
|
890
|
+
table `rapt_exchrate`: the rates are data of the application). A service definition that
|
|
891
|
+
exposes a standard view receives it too.
|
|
892
|
+
- **Tests**: `compiler/test/standard.test.ts`, `runtime/test/standard-views.test.ts` (read,
|
|
893
|
+
texts, read-only, value help in the browser, calendar, users).
|
|
894
|
+
|
|
895
|
+
## D45 — `standard` and `enhanced` modes
|
|
896
|
+
|
|
897
|
+
- **Decision**: `rapt/config.json` (`{ "mode": "enhanced" }`) or `--mode` chooses the mode of
|
|
898
|
+
the project. `standard` (default): the sources behave as on SAP and what SAP refuses is
|
|
899
|
+
refused; a project that builds in this mode imports and activates on SAP. `enhanced`: the
|
|
900
|
+
extensions of Open RAP Runtime, which do not exist on SAP:
|
|
901
|
+
- managed BO with unmanaged save on a custom entity (D43): error BDEF027 in `standard`,
|
|
902
|
+
information in `enhanced`;
|
|
903
|
+
- `field ( mandatory )` checked on save without a validation: each created or changed instance
|
|
904
|
+
with an initial mandatory field fails, with one message per field (`%element`). In
|
|
905
|
+
`standard`, as in RAP (D20), only `mandatory : create` is checked.
|
|
906
|
+
- **Why a file**: the mode is a property of the project, versioned with its sources; the CLI,
|
|
907
|
+
`rapt dev` and the VS Code extension (command *Choose the project mode*) read it.
|
|
908
|
+
- **Tests**: `compiler/test/config.test.ts`, `runtime/test/custom-entity.test.ts`.
|
|
909
|
+
|
|
910
|
+
## D46 — `rapt generate`: one object at a time, derived from the existing objects
|
|
911
|
+
|
|
912
|
+
- **Decision**: `rapt generate <src> <type> <name> [--from X] [--for Y] [--expose A,B] [--root]`
|
|
913
|
+
writes an object with the file names of abapGit, then its XML: table, view (from a table,
|
|
914
|
+
elements in CamelCase, administrative annotations), projection (associations redirected by the
|
|
915
|
+
same renaming: `ZC_Travel ← ZR_Travel` gives `ZC_Booking`), metadata extension, access control,
|
|
916
|
+
BDEF (managed with its compositions: locks, authorizations and ETag of the children, UUID
|
|
917
|
+
numbering, read-only administrative fields, mapping; or projection BDEF), behavior pool (one
|
|
918
|
+
handler per entity, global authorization), abstract entity, custom entity and its query class,
|
|
919
|
+
class, interface, service definition and binding. An existing file is never overwritten.
|
|
920
|
+
- **Why**: the creation wizards of ADT derive an object from the previous one; `rapt new` writes
|
|
921
|
+
a whole BO, `rapt generate` accompanies the construction step by step.
|
|
922
|
+
- **VS Code**: *New RAP object…* (type, source object picked in a list, proposed name).
|
|
923
|
+
- **Tests**: `compiler/test/generate.test.ts` (a complete BO built command by command, without a
|
|
924
|
+
diagnostic), `vscode/test/rapt.test.ts`.
|
|
925
|
+
|
|
926
|
+
## D47 — MCP server of the service
|
|
927
|
+
|
|
928
|
+
- **Decision**: `rapt mcp <src>` writes `rapt/mcp.json`, one tool per operation of the service
|
|
929
|
+
(`<Set>_list`, `_get`, `_create`, `_update`, `_delete`, `_create<_Composition>`, `_<action>`),
|
|
930
|
+
each with a description to complete to explain to an agent what the tool does and when to use
|
|
931
|
+
it; `false` removes a tool. Running the command again keeps the written descriptions, adds the
|
|
932
|
+
new tools and removes those the service no longer has. The application built with this file
|
|
933
|
+
serves the tools on `/mcp` (Model Context Protocol, "Streamable HTTP" transport, JSON
|
|
934
|
+
responses, without an SSE stream).
|
|
935
|
+
- **Execution**: each call is an OData request run by the service, for the user of the MCP
|
|
936
|
+
request: authorizations, DCL, feature control, validations and messages are those of the
|
|
937
|
+
OData. The errors of the BO come back as text (`isError`), the result as `structuredContent`.
|
|
938
|
+
- **Authentication**: bearer token (OIDC) or development user; never the session cookie of the
|
|
939
|
+
browser (a web page could otherwise call the tools on behalf of the user).
|
|
940
|
+
- **Tests**: `compiler/test/mcp.test.ts`, `runtime/test/mcp.test.ts` (protocol, read, write with
|
|
941
|
+
messages, actions, create by composition).
|
|
942
|
+
|
|
943
|
+
## D48 — Spaces and pages of the launchpad
|
|
944
|
+
|
|
945
|
+
- **Decision**: `launchpad.json` accepts `spaces` (title, roles) → `pages` → `sections` of tiles,
|
|
946
|
+
like the SAP Fiori launchpad in spaces mode. A user sees the spaces of their roles as tabs, the
|
|
947
|
+
pages of a space in a list; the last space is remembered (local storage of the browser); the
|
|
948
|
+
search covers all the spaces. A file with `groups` only gives a space of one page. `groups` of
|
|
949
|
+
the model stays the list of all the sections (intents, search).
|
|
950
|
+
- **Tests**: `compiler/test/launchpad.test.ts`, `runtime/test/launchpad-ui.test.ts`.
|
|
951
|
+
|
|
952
|
+
## D49 — Development launchpad and documentation application
|
|
953
|
+
|
|
954
|
+
- **Decision**: `rapt dev` serves at `/` a development launchpad: the application (with the
|
|
955
|
+
number of instances of its list), the documentation of Open RAP Runtime, the `$metadata` and,
|
|
956
|
+
with `rapt/mcp.json`, the MCP server. The application and the documentation show the shell bar
|
|
957
|
+
there. The documentation is an OpenUI5 application (list of the pages, search, links between
|
|
958
|
+
pages) whose pages are rendered from the Markdown of the repository at build time (our own
|
|
959
|
+
renderer, without a dependency); it is copied next to the compiler (`packages/compiler/docs`)
|
|
960
|
+
so that the packaged rapt (npm, VS Code extension) has it too. None of this is in
|
|
961
|
+
`rapt build`: it is a development tool.
|
|
962
|
+
- **Tests**: `compiler/test/docs.test.ts`, `compiler/test/dev.test.ts`.
|
|
963
|
+
|
|
964
|
+
## D50 — Deployment outside localhost
|
|
965
|
+
|
|
966
|
+
- **Decision**: the generated application and launchpad deploy as is anywhere Node.js 22 or a
|
|
967
|
+
container runs. In addition to the Dockerfile, they have a Cloud Foundry `manifest.yml` (SAP
|
|
968
|
+
BTP) and a `.cfignore`. The runtime reads the environment of Cloud Foundry: `PUBLIC_URL` from
|
|
969
|
+
the route (`VCAP_APPLICATION`), `DATABASE_URL` and `DATABASE_CA` from a bound PostgreSQL
|
|
970
|
+
service (`VCAP_SERVICES`); a variable given explicitly wins. `DATABASE_CA` gives the
|
|
971
|
+
certificate of the PostgreSQL server (verified TLS connection). `rapt dev --host 0.0.0.0` opens
|
|
972
|
+
the development server to the network. Guide: [DEPLOYMENT.md](DEPLOYMENT.md).
|
|
973
|
+
- **Tests**: `runtime/test/platform.test.ts`, `compiler/test/app-scaffold.test.ts`.
|
|
974
|
+
|
|
975
|
+
## D51 — UI: filter bar, user variants, refresh
|
|
976
|
+
|
|
977
|
+
- **Filter bar**: the *Go*, *Clear* and *Adapt Filters* buttons are aligned with the fields. Each
|
|
978
|
+
filter accepts conditions as in SAP Fiori (`=x`, `!=x`, `>x`, `>=x`, `<x`, `<=x`, `a...b`,
|
|
979
|
+
`*x*`, `x*`, `*x`; a single value: "contains" for a text, "equal" otherwise), several per field
|
|
980
|
+
(tokens: included values combined with OR, exclusions with AND); a string of digits (NUMC) is
|
|
981
|
+
entered without its leading zeros; dates as ranges, date-times by whole days (UTC). *Adapt
|
|
982
|
+
Filters* adds the columns of the list as filters. The "Filtered By" summary shows when the
|
|
983
|
+
header is collapsed.
|
|
984
|
+
- **Variants**: `sap.m.VariantManagement` (OpenUI5): filters, visible filters, visible columns
|
|
985
|
+
and sorting (*table settings*) saved per user by the service (`$variants?page=…`, table
|
|
986
|
+
`rapt_ui_variant` created on first use), default variant, favorites, renaming and deletion; in
|
|
987
|
+
the local storage of the browser when the service does not keep them.
|
|
988
|
+
- **Refresh**: the list reloads when you come back to it after a change saved on an Object Page
|
|
989
|
+
(deletion, save, action); the title of the table gives the number of rows.
|
|
990
|
+
- **Object Page**: breadcrumbs to the list; unsaved changes are discarded (with a confirmation)
|
|
991
|
+
when leaving the page, and are no longer sent with the next save.
|
|
992
|
+
- **Tests**: `compiler/test/ui.test.ts` (conditions), `runtime/test/ui.test.ts` (in the browser).
|
|
993
|
+
|
|
994
|
+
## D52 — `$apply`: aggregation in the OData runtime
|
|
995
|
+
|
|
996
|
+
- **Context**: the Analytical List Page and the Overview Page (D53) need totals and groups;
|
|
997
|
+
Fiori Elements requests them with `$apply` (OData Data Aggregation).
|
|
998
|
+
- **Decision**: the runtime reads `$apply` with `filter(…)`, then a `groupby((P,…))`,
|
|
999
|
+
`groupby((P,…),aggregate(…))` or `aggregate(…)` transformation; methods `sum`, `min`, `max`,
|
|
1000
|
+
`average`, `countdistinct` and `$count as Alias`. The SQL query is a `GROUP BY` on the view of
|
|
1001
|
+
the entity, with the access controls (DCL) of the user; `$orderby` on the dimensions and the
|
|
1002
|
+
aliases, `$top`, `$skip`, `$count`. The other transformations (`topcount`, `compute`, a
|
|
1003
|
+
transformation after the grouping…) answer 501.
|
|
1004
|
+
- **Why**: it is the part of OData Data Aggregation the floorplans use; the rest requires the
|
|
1005
|
+
`@Aggregation` annotation transactional BOs do not have.
|
|
1006
|
+
- **Tests**: `runtime/test/odata-apply.test.ts`.
|
|
1007
|
+
|
|
1008
|
+
## D53 — Floorplans: Worklist, Analytical List Page, Overview Page, Form entry
|
|
1009
|
+
|
|
1010
|
+
- **Context**: Fiori Elements chooses the floorplan in the descriptor of the application; the
|
|
1011
|
+
RAP sources say nothing about it. The floorplan is therefore a setting of the project.
|
|
1012
|
+
- **Decision**: `ui` of `rapt/config.json`:
|
|
1013
|
+
`{ "floorplans": { "<EntitySet>": "listReport" | "worklist" | "analyticalListPage" | "formEntry" },
|
|
1014
|
+
"overview": { "title": "…", "cards": [ … ] } }`.
|
|
1015
|
+
- *Worklist*: list without a filter bar, one tab per qualified `@UI.selectionVariant` of the
|
|
1016
|
+
entity, with the number of instances of each tab;
|
|
1017
|
+
- *Analytical List Page*: the first `@UI.chart` of the entity (dimension, measures, `#COLUMN`,
|
|
1018
|
+
`#BAR`, `#LINE`, `#PIE`, `#DONUT`) above the table, read with `$apply` (D52); a value chosen
|
|
1019
|
+
in the chart filters the table; chart view, table view or both;
|
|
1020
|
+
- *Form entry*: the Object Page for creation only; after the save, a new empty form;
|
|
1021
|
+
- *Overview Page*: home page of cards (`kpi`: aggregate of a measure or count; `list`,
|
|
1022
|
+
`table`: filtered and sorted instances; `chart`: a `@UI.chart`), each card opens the list or
|
|
1023
|
+
the instance.
|
|
1024
|
+
The chart is our own OpenUI5 control in SVG (`sap.viz` only exists in SAPUI5).
|
|
1025
|
+
- **Why**: the same annotations as Fiori Elements (`UI.chart`, `UI.selectionVariant`), without a
|
|
1026
|
+
proprietary library; a checked setting (CFG003) rather than a descriptor to write.
|
|
1027
|
+
- **Tests**: `compiler/test/config.test.ts`, `compiler/test/ui.test.ts`,
|
|
1028
|
+
`runtime/test/ui-floorplans.test.ts` (browser).
|
|
1029
|
+
|
|
1030
|
+
## D54 — Draft (`with draft`)
|
|
1031
|
+
|
|
1032
|
+
- **Context**: draft is the standard way to edit in Fiori Elements V4; a managed RAP BO declares
|
|
1033
|
+
it with `with draft;` and one `draft table` per entity.
|
|
1034
|
+
- **Decision**:
|
|
1035
|
+
- **BDEF**: `with draft`, `draft table`, `total etag`, `draft action Edit`, `Activate
|
|
1036
|
+
optimized`, `Discard`, `Resume`, `draft determine action Prepare`; an entity without a
|
|
1037
|
+
`draft table`: BDEF028.
|
|
1038
|
+
- **Database**: one draft table per entity (the elements of the entity, `DraftUUID`,
|
|
1039
|
+
`HasActiveEntity`, dates of the draft) and `rapt_draft_admin` (user, creation, last change);
|
|
1040
|
+
a `<projection>_all` view combines active instances and drafts.
|
|
1041
|
+
- **OData**: `IsActiveEntity` key, properties `HasActiveEntity`, `HasDraftEntity`, navigation
|
|
1042
|
+
`DraftAdministrativeData`, actions `Edit`, `Activate`, `Prepare`, `Resume`, `Discard`,
|
|
1043
|
+
`Common.DraftRoot` / `DraftNode`. A user only reads their own drafts.
|
|
1044
|
+
- **Runtime**: creation, change and deletion in the draft without determinations nor
|
|
1045
|
+
validations; `Edit` copies the instance and its compositions (one draft per instance,
|
|
1046
|
+
exclusive lock of 15 minutes as SAP); `Activate` replays the draft in the managed runtime in
|
|
1047
|
+
the LUW of the request (determinations, validations, saver), keys kept, then deletes the
|
|
1048
|
+
draft; an active instance that has a draft cannot be changed directly (409).
|
|
1049
|
+
- **UI**: Create opens a draft, each field is saved in it when leaving it, Save activates,
|
|
1050
|
+
Cancel and *Discard Draft* discard; the list shows the editing status.
|
|
1051
|
+
- **Why**: the observable behavior of SAP (URLs, keys, actions, lock) so that the sources and the
|
|
1052
|
+
clients stay those of an SAP system.
|
|
1053
|
+
- **Tests**: `runtime/test/draft.test.ts`, `runtime/test/ui-draft.test.ts` (browser),
|
|
1054
|
+
`compiler/test/ir.test.ts` (BDEF028).
|
|
1055
|
+
|
|
1056
|
+
## D55 — Table entities, CDS simple types and enumerated types
|
|
1057
|
+
|
|
1058
|
+
- **Context**: recent ABAP Cloud adds CDS table entities (`define table entity`: a table and its
|
|
1059
|
+
entity in one object, persistence of a RAP BO) and CDS types (`define type`, DRTY objects):
|
|
1060
|
+
simple types and enumerated types (`enum`). SAP does not expose enumerated types in RAP OData
|
|
1061
|
+
services ("Development Constraints" of RAP).
|
|
1062
|
+
- **Decision**:
|
|
1063
|
+
- **CDS types** (`<name>.drty.acds`, read by Open RAP Runtime: abaplint knows the object without
|
|
1064
|
+
parsing it): a simple type is worth its base type (built-in type, data element or another CDS
|
|
1065
|
+
type), with its label; an enumerated type has a base type (`abap.int1`, `int2`, `int4`,
|
|
1066
|
+
`char(1..8)`, `numc(1..8)`), exactly one `initial` constant, unique constants and values
|
|
1067
|
+
(TYPE001 to TYPE003). A type typed by an enumerated type takes its constants. They type the
|
|
1068
|
+
elements of the table entities, of the abstract and custom entities.
|
|
1069
|
+
- **Table entity**: a table with the same name (elements in lower case; `NOT NULL` unless
|
|
1070
|
+
`null`; keys first; `abap.string` declared `null`, CDS019-CDS020) and a view entity on this
|
|
1071
|
+
table. A managed BDEF on the table entity does not write `persistent table`: the table entity
|
|
1072
|
+
is its persistence. Views and projections read it as an entity; the ABAP SQL of the behavior
|
|
1073
|
+
pool reads and writes its table.
|
|
1074
|
+
- **Enumerated values**: stored in the database with their base value. In the behavior pool,
|
|
1075
|
+
the type is given to the transpiler as a data element of its base type and each
|
|
1076
|
+
`type-constant` constant becomes the literal of its value (same line, same meaning).
|
|
1077
|
+
- **OData**: in `standard` mode, an enumerated element in an exposed entity is an error
|
|
1078
|
+
(CDS021), as on SAP. In `enhanced` mode, it becomes an `EnumType` of the `$metadata`: members
|
|
1079
|
+
named like the constants, integer value (the base value when it is an integer, the position
|
|
1080
|
+
otherwise), label as `Core.Description`. The JSON carries the name of the member
|
|
1081
|
+
(`"Status": "OPEN"`); `$filter` accepts `Status eq 'OPEN'`, `Ns.Type'OPEN'` and the integer
|
|
1082
|
+
value; a value outside the type is refused (400). The UI shows the labels and offers a choice
|
|
1083
|
+
list (OpenUI5 cannot write enumeration literals: the filter goes through `$filter`).
|
|
1084
|
+
- `rapt generate tableentity|type|enum`, DDLS of type `T` for `rapt abapgit`. A CDS type is an
|
|
1085
|
+
object in the AFF format of abapGit (SAP/abap-file-formats repository): the source
|
|
1086
|
+
`<name>.drty.acds` and its metadata `<name>.drty.json` (`formatVersion`, description,
|
|
1087
|
+
language), written by `rapt abapgit` (`.drty.asdrty` is still read).
|
|
1088
|
+
- **Why**: the table entity is a complete BO in two files; enumerated types give named values to
|
|
1089
|
+
the API without changing the storage. The `standard` mode stays activatable on SAP, the
|
|
1090
|
+
exposure as an enumeration is an extension (D45).
|
|
1091
|
+
- **Tests**: `compiler/test/table-entity.test.ts`, `runtime/test/table-entity.test.ts` (OData,
|
|
1092
|
+
ABAP, browser), fixture `fixtures/table-entity`.
|
|
1093
|
+
|
|
1094
|
+
## D56 — Value helps: bindings, validation, fixed values, action parameters
|
|
1095
|
+
|
|
1096
|
+
- **Context**: D44 gave the simple value help (`entity: { name, element }`); RAP BOs also use
|
|
1097
|
+
`additionalBinding`, `useForValidation` and drop-down lists.
|
|
1098
|
+
- **Decision**:
|
|
1099
|
+
- `additionalBinding: [{ localElement, element, usage }]`: `#FILTER` restricts the list by the
|
|
1100
|
+
local value (`Common.ValueListParameterIn`), `#RESULT` copies the element of the chosen row
|
|
1101
|
+
into the local element (`ValueListParameterOut`), without a usage both (`InOut`);
|
|
1102
|
+
- `useForValidation: true`: `Common.ValueListForValidation`; the UI reports an entered value
|
|
1103
|
+
missing from the list (error state of the field);
|
|
1104
|
+
- `@ObjectModel.resultSet.sizeCategory: #XS` on the entity of the value help:
|
|
1105
|
+
`Common.ValueListWithFixedValues`, and a drop-down list (value and description) in the UI;
|
|
1106
|
+
- the action parameters (abstract entity) have their value help in the dialog, if its entity is
|
|
1107
|
+
exposed by the service (as in RAP, which does not add it implicitly).
|
|
1108
|
+
- **Why**: they are the value help forms of common RAP BOs, and Fiori Elements interprets them
|
|
1109
|
+
from the `$metadata`.
|
|
1110
|
+
- **Tests**: `compiler/test/table-entity.test.ts` (CSDL), `runtime/test/table-entity.test.ts`
|
|
1111
|
+
(drop-down list that fills the currency, validation, in the browser),
|
|
1112
|
+
`runtime/test/standard-views.test.ts`.
|
|
1113
|
+
|
|
1114
|
+
## D57 — Display annotations: criticality, texts, header, links, filters
|
|
1115
|
+
|
|
1116
|
+
- **Context**: the generated UI followed the structure annotations (`lineItem`,
|
|
1117
|
+
`identification`, `facet`…) but not the common display annotations of RAP BOs; the complete
|
|
1118
|
+
table is in [ANNOTATIONS.md](ANNOTATIONS.md).
|
|
1119
|
+
- **Decision**:
|
|
1120
|
+
- `criticality` (line item, identification, data point): colored `ObjectStatus` or
|
|
1121
|
+
`ObjectNumber` (1 negative, 2 critical, 3 positive);
|
|
1122
|
+
- `@ObjectModel.text.element` + `@UI.textArrangement`: "Text (ID)" (default, as in Fiori
|
|
1123
|
+
Elements), "ID (Text)", text only, separate;
|
|
1124
|
+
- `@UI.dataPoint` (`UI.DataPoint`, qualified by its `qualifier` or the name of the element) and
|
|
1125
|
+
`@UI.facet` purpose `#HEADER` (`#DATAPOINT_REFERENCE`, `#FIELDGROUP_REFERENCE`): header of
|
|
1126
|
+
the Object Page, progress bar (`#PROGRESS`, `targetValue`), rating (`#RATING`), number;
|
|
1127
|
+
- `@UI.multiLineText`: text area; `@Semantics.email.address`, `@Semantics.telephone.type`:
|
|
1128
|
+
`mailto:` / `tel:` links (`Communication` vocabulary); `#WITH_URL` line item: link;
|
|
1129
|
+
- `@Consumption.filter.defaultValue` (initial filter, applied by the standard variant),
|
|
1130
|
+
`mandatory` (mandatory filter, `FilterRestrictions`), `selectionType` /
|
|
1131
|
+
`multipleSelections` (one value);
|
|
1132
|
+
- `@Consumption.hidden`: element not exposed, except a key or the element of an association.
|
|
1133
|
+
- Fix: `@UI.facet: […]` then `@UI: { … }` on the same element keep both (the second
|
|
1134
|
+
annotation replaced the first one).
|
|
1135
|
+
- **Why**: they are the annotations the SAP examples (RAP100, Flight) use on each BO; they change
|
|
1136
|
+
what the user sees without changing the BO.
|
|
1137
|
+
- **Tests**: `compiler/test/table-entity.test.ts` (CSDL), `runtime/test/table-entity.test.ts`
|
|
1138
|
+
(browser), fixture `fixtures/table-entity`.
|
|
1139
|
+
|
|
1140
|
+
## D58 — Draft UI as in Fiori Elements
|
|
1141
|
+
|
|
1142
|
+
- **Context**: the first draft UI (D54) edited and activated, but without the landmarks of Fiori
|
|
1143
|
+
Elements: the list showed the active instance even when the user had a draft, without an
|
|
1144
|
+
editing status filter nor a saving indicator.
|
|
1145
|
+
- **Decision**:
|
|
1146
|
+
- **List Report**: *Editing Status* filter (*All*, *Own Draft*, *Locked by Another User*,
|
|
1147
|
+
*No Changes*); *All* shows the drafts of the user instead of their active instances
|
|
1148
|
+
(`IsActiveEntity eq false or HasDraftEntity eq false or
|
|
1149
|
+
DraftAdministrativeData/InProcessByUser ne '<user>'`, like `SiblingEntity` at SAP); *Draft*
|
|
1150
|
+
and *Locked by X* markers (`ObjectMarker`). The active instance points to the administrative
|
|
1151
|
+
data of its draft (`DraftUUID`), as at SAP.
|
|
1152
|
+
- **Object Page**: *Draft* marker in the header, *Locked by X* on an instance another user is
|
|
1153
|
+
editing, `DraftIndicator` in the footer ("Saving draft…", "Draft updated"); *Discard Draft*
|
|
1154
|
+
asks "Discard all changes?"; leaving the page in draft offers *Keep Draft* / *Discard Draft*
|
|
1155
|
+
(the draft stays in the list).
|
|
1156
|
+
- The current user is given to the UI by `$permissions` (`$user`).
|
|
1157
|
+
- **Why**: a Fiori Elements user finds the same gestures; no SAP library (OpenUI5 controls
|
|
1158
|
+
`ObjectMarker`, `DraftIndicator`).
|
|
1159
|
+
- **Tests**: `runtime/test/ui-draft.test.ts` (browser).
|
|
1160
|
+
|
|
1161
|
+
## D59 — Collaborative draft
|
|
1162
|
+
|
|
1163
|
+
- **Context**: `with collaborative draft;` and `draft action Share;` (ABAP Cloud) let several
|
|
1164
|
+
users edit the same draft; the users invited with `Share` or who join the draft change it,
|
|
1165
|
+
activate it or discard it.
|
|
1166
|
+
- **Decision**:
|
|
1167
|
+
- BDEF: `with collaborative draft;` in the header, `draft action Share;` mandatory (BDEF029),
|
|
1168
|
+
`use action Share;` in the projection.
|
|
1169
|
+
- Database: `rapt_draft_user` (draft, user, role `O` owner / `E` editor).
|
|
1170
|
+
- OData: action `Share(Users: Collection(DraftAdministrativeUserInput{UserID, UserAccessRole}),
|
|
1171
|
+
ShareAll)`, `Common.DraftRoot/ShareAction`, entity `DraftAdministrativeUser` (navigation
|
|
1172
|
+
`DraftAdministrativeData/DraftAdministrativeUser`).
|
|
1173
|
+
- Runtime: the creator is the owner; `Edit` of an instance whose draft another user has makes
|
|
1174
|
+
the user join the draft (instead of 409); every user of the draft reads it, changes it,
|
|
1175
|
+
activates it, discards it; the others receive 409 / 404 as for an exclusive draft.
|
|
1176
|
+
- UI: in draft, the users (avatars) and *Invite* (`Share`); the page reads the draft again
|
|
1177
|
+
every 5 seconds to show the changes of the others.
|
|
1178
|
+
- **Why**: the behavior of SAP (actions, entities, annotation) so that Fiori Elements and the
|
|
1179
|
+
OData clients find their way; the periodic refresh replaces the WebSockets (ABAP Channels) of
|
|
1180
|
+
SAP.
|
|
1181
|
+
- **Tests**: `runtime/test/draft-collaborative.test.ts` (OData, browser, BDEF029).
|
|
1182
|
+
|
|
1183
|
+
## D60 — Web APIs: OData V4 - Web API binding, OAuth 2.0 (client credentials, PKCE)
|
|
1184
|
+
|
|
1185
|
+
- **Context**: a RAP service is also exposed as an API for other systems (*OData V4 - Web API*
|
|
1186
|
+
binding). The callers authenticate with OAuth 2.0: *client credentials* (machine to machine)
|
|
1187
|
+
or *authorization code* with PKCE (application on behalf of a user).
|
|
1188
|
+
- **Decision**:
|
|
1189
|
+
- **Binding**: `BINDING_TYPE_CATEGORY` 1 of the SRVB, or `--binding-type webapi`, or
|
|
1190
|
+
`rapt generate binding … --binding-type webapi`. As at SAP: no `UI.*` annotation, nor value
|
|
1191
|
+
help, nor side effects, nor feature control (`__EntityControl`…) in the service (the checks
|
|
1192
|
+
are still applied by the runtime); no generated UI; a draft BO only exposes its active
|
|
1193
|
+
instances (direct writes, without `IsActiveEntity`).
|
|
1194
|
+
- **Production** (`AUTH_MODE=oidc`): the service accepts the tokens of the IdP (Keycloak, SAP
|
|
1195
|
+
Cloud Identity Services, Entra ID…) obtained with either flow; a *client credentials* token
|
|
1196
|
+
without `preferred_username` designates its client (`client_id`) as a technical user; its
|
|
1197
|
+
roles can come from `scope` (`OIDC_ROLES_CLAIM=scope`). The browser sessions already used the
|
|
1198
|
+
PKCE flow.
|
|
1199
|
+
- **Development**: `rapt dev` (and an application started with `AUTH_MODE=none` and
|
|
1200
|
+
`DEV_OAUTH=on`) serves a development authorization server under `/oauth`: OIDC discovery,
|
|
1201
|
+
JWKS, `/oauth/token` (`client_credentials` with `rapt-client` / `rapt-secret`,
|
|
1202
|
+
`authorization_code` with S256 PKCE verification), `/oauth/authorize` (page to choose the user
|
|
1203
|
+
and their roles); configurable clients (`DEV_OAUTH_CLIENTS`). Its tokens (RS256, key generated
|
|
1204
|
+
on start) give a development user.
|
|
1205
|
+
- **Why**: test an API exactly as its clients will call it (Postman, Bruno, another system)
|
|
1206
|
+
without installing an IdP; in production, the IdP stays the only source of tokens.
|
|
1207
|
+
- **Tests**: `runtime/test/web-api.test.ts` (binding, client credentials, PKCE),
|
|
1208
|
+
`runtime/test/auth.test.ts` (client token).
|
|
1209
|
+
|
|
1210
|
+
## D61 — Guided creation of the objects, floorplan, CLI / extension parity
|
|
1211
|
+
|
|
1212
|
+
- **Context**: ADT guides the creation of an object (*New ABAP Repository Object*: types by
|
|
1213
|
+
category, description, reference object); the extension only offered a series of quick picks,
|
|
1214
|
+
and the floorplan of the UI could only be chosen by editing `rapt/config.json`.
|
|
1215
|
+
- **Decision**:
|
|
1216
|
+
- **Catalog**: `authoring/catalog.ts` describes each object type (category, label,
|
|
1217
|
+
description, files, length of the name, fields: source object, exposed entities, binding type,
|
|
1218
|
+
built-in type); `rapt generate <src> --json` without a type returns it. The extension builds
|
|
1219
|
+
its wizard from it: the CLI stays the only source.
|
|
1220
|
+
- **Wizard** (*New RAP object* webview): filter, types by category, form of the type (the
|
|
1221
|
+
objects of the project offered as source), *Create* runs `rapt generate` (or `rapt new` for a
|
|
1222
|
+
complete BO) and opens the file. New types: data element and domain (`--type`).
|
|
1223
|
+
- ***RAP Objects* view**: the objects of the project grouped like the project explorer of ADT.
|
|
1224
|
+
- **Floorplan**: `rapt floorplan <src> [<EntitySet> <floorplan>]` reads and writes
|
|
1225
|
+
`ui.floorplans` of `rapt/config.json`; the VS Code command describes each floorplan (use,
|
|
1226
|
+
required annotations).
|
|
1227
|
+
- **Parity**: each command of the CLI has its VS Code command (`check`, `ir`, `build`,
|
|
1228
|
+
`launchpad`, `dev`, `new`, `abapgit`, `mcp`, `generate`, `floorplan`).
|
|
1229
|
+
- **Why**: an ABAP developer finds the gestures of ADT; the choice of the floorplan becomes
|
|
1230
|
+
explicit and documented at the moment it is made.
|
|
1231
|
+
- **Tests**: `vscode/test/rapt.test.ts` (catalog, arguments of the wizard, `rapt floorplan`,
|
|
1232
|
+
groups), wizard checked in Chromium.
|
|
1233
|
+
|
|
1234
|
+
## D62 — Website of the project and download of the extension
|
|
1235
|
+
|
|
1236
|
+
- **Decision**: `site/` (static HTML and CSS, without a dependency nor a build tool) presents the
|
|
1237
|
+
project; the `site.yml` workflow publishes it on GitHub Pages with the screenshots of
|
|
1238
|
+
`docs/images` and the `.vsix` built from the same commit (*Download* button). The tagged
|
|
1239
|
+
versions are still published by `release.yml` (GitHub Release, Marketplace and Open VSX when
|
|
1240
|
+
their tokens are configured). To enable once: *Settings → Pages → Source: GitHub Actions*.
|
|
1241
|
+
- **Why**: one address to share, always up to date, without a server to maintain.
|
|
1242
|
+
- **Tests**: rendering checked in Chromium (desktop, mobile without horizontal scrolling, dark
|
|
1243
|
+
mode).
|
|
1244
|
+
|
|
1245
|
+
## D63 — English everywhere; documentation pages on the website
|
|
1246
|
+
|
|
1247
|
+
- **Context**: the project was documented in French, and the website linked to pages of the
|
|
1248
|
+
GitHub repository, which is private: visitors could not read the guide.
|
|
1249
|
+
- **Decision**:
|
|
1250
|
+
- **English**: documentation, website, VS Code extension (commands, wizard, settings), object
|
|
1251
|
+
and floorplan catalog, messages and comments are in English (AGENTS.md rule 8). The documents
|
|
1252
|
+
with French names were renamed (`DEPLOYMENT.md`, `UTILITIES.md`, `REMOTE_APIS.md`).
|
|
1253
|
+
- **Documentation on the website**: `scripts/build-site.mjs` renders each Markdown document of
|
|
1254
|
+
the documentation app (same list and order as `rapt dev`, D49) into a static page
|
|
1255
|
+
`docs/<name>.html` of the website, with the Markdown renderer of the compiler, a navigation
|
|
1256
|
+
between the pages and the style of the website. Links between documents point to the rendered
|
|
1257
|
+
pages; links to files of the repository that are not published (sources, fixtures) are shown
|
|
1258
|
+
as plain text. The `site.yml` workflow runs the script after `pnpm build`. The website no
|
|
1259
|
+
longer links to GitHub.
|
|
1260
|
+
- **Why**: a private repository cannot be the documentation of a public website; rendering from
|
|
1261
|
+
the same Markdown keeps a single source, already shown in `rapt dev`.
|
|
1262
|
+
- **Tests**: `compiler/test/docs.test.ts` (rendering of the links for the website), website
|
|
1263
|
+
checked in Chromium (pages, navigation, mobile, dark mode).
|
|
1264
|
+
|
|
1265
|
+
## D64 — rapt for AI coding agents: `rapt agent`, `rapt` in the terminals of VS Code
|
|
1266
|
+
|
|
1267
|
+
- **Context**: AI agents (GitHub Copilot in agent mode, Claude Code, Cursor…) write more and more
|
|
1268
|
+
of the RAP sources. With a shell, they can run `rapt check --json`; but in VS Code `rapt` was
|
|
1269
|
+
only inside the extension (not on the PATH), and the documentation of Open RAP Runtime (codes,
|
|
1270
|
+
annotations, known gaps and their workarounds) is not in the project of the user, so an agent
|
|
1271
|
+
could not find it. Also, the `.vsix` lost this documentation: the staging of the extension
|
|
1272
|
+
removed the `docs` folders and the `.md` files of the packages, and the documentation app of
|
|
1273
|
+
`rapt dev` was empty when started from the extension.
|
|
1274
|
+
- **Decision**:
|
|
1275
|
+
- **`rapt agent [<workspace>]`**: an MCP server over stdio (newline-delimited JSON-RPC) with the
|
|
1276
|
+
tools `rapt_projects`, `rapt_check` (diagnostics, without writing), `rapt_object_types` (the
|
|
1277
|
+
catalog of D61), `rapt_generate`, `rapt_new_business_object`, `rapt_floorplan`,
|
|
1278
|
+
`rapt_abapgit`, `rapt_docs_search` (sections of the documentation by relevance) and
|
|
1279
|
+
`rapt_docs_read` (a page or a section, as Markdown), and instructions given on
|
|
1280
|
+
`initialize` (check after each change, standard mode, read the documentation). The paths are
|
|
1281
|
+
relative to the workspace; a path outside it is refused. The project is given, or the only
|
|
1282
|
+
one of the workspace. Distinct from `rapt mcp` (D47), the MCP server of a *service*.
|
|
1283
|
+
- **VS Code**: the extension registers `rapt agent` for the agent mode of VS Code
|
|
1284
|
+
(`vscode.lm.registerMcpServerDefinitionProvider`, contribution `mcpServerDefinitionProviders`,
|
|
1285
|
+
one server per workspace folder, setting `rapt.agent.enabled`); and it puts `rapt` on the
|
|
1286
|
+
PATH of its integrated terminals (`environmentVariableCollection`): shims `rapt` (sh) and
|
|
1287
|
+
`rapt.cmd` (Windows) in the storage of the extension start the CLI the extension uses, with
|
|
1288
|
+
the Node.js of VS Code (setting `rapt.terminal.addToPath`). An agent of the terminal (Claude
|
|
1289
|
+
Code) then runs `rapt` or `claude mcp add rapt -- rapt agent .` without installing anything.
|
|
1290
|
+
- **Documentation in the `.vsix`**: the staging keeps `@open-rap/compiler/docs` and fails
|
|
1291
|
+
when it is missing; the documentation app shows "Documentation not found" instead of an empty
|
|
1292
|
+
list if the documentation is ever missing.
|
|
1293
|
+
- **Why an MCP server and not only the CLI**: the CLI with `--json` and the AGENTS.md of the
|
|
1294
|
+
project are enough for an agent with a shell; the MCP server adds what the CLI does not give:
|
|
1295
|
+
discovery without configuration in VS Code, typed inputs (the catalog of the objects as enums),
|
|
1296
|
+
and the documentation of Open RAP Runtime, searchable, wherever the extension or the npm package
|
|
1297
|
+
is installed. It reuses the functions of the CLI: one engine (D39).
|
|
1298
|
+
- **Tests**: `compiler/test/agent.test.ts` (protocol, business object created, checked, object
|
|
1299
|
+
generated, floorplan, abapGit, paths outside the workspace, several projects, documentation,
|
|
1300
|
+
stdio of the built CLI), `vscode/test/rapt.test.ts` (shims run `rapt check` through the PATH,
|
|
1301
|
+
server definitions), `compiler/test/docs.test.ts` (empty documentation); `.vsix` built and its
|
|
1302
|
+
`rapt dev` checked to serve the 14 documentation pages.
|
|
1303
|
+
|
|
1304
|
+
## D65 — npm package `open-rap-runtime`
|
|
1305
|
+
|
|
1306
|
+
- **Context**: outside VS Code (CI pipelines, a terminal agent, another editor), rapt needed a
|
|
1307
|
+
clone of the repository.
|
|
1308
|
+
- **Decision**: the package `open-rap-runtime` (`packages/cli`) gives the `rapt` command
|
|
1309
|
+
(`npm install -g open-rap-runtime`, `npx open-rap-runtime check .`, or a `devDependency` of the
|
|
1310
|
+
project and `npx rapt …`). It depends on `@open-rap/compiler` and
|
|
1311
|
+
`@open-rap/runtime` and on what `rapt dev` serves (OpenUI5, PGlite, less-openui5, at the
|
|
1312
|
+
versions of the runtime, checked by a test), like the `rapt/` of the extension (D39). Its
|
|
1313
|
+
`dist/rapt.js` calls `main` of `@open-rap/compiler/cli` (new export); `cli.js` runs
|
|
1314
|
+
itself only when it is the program (`isEntryPoint`: real paths compared, symbolic links and
|
|
1315
|
+
Windows paths included), so the command runs once. The compiler no longer declares a `bin`:
|
|
1316
|
+
one package gives `rapt`. `COMPILER_VERSION` is read from the `package.json` of the compiler.
|
|
1317
|
+
Each package has a README for npm; `@open-rap/model`, `compiler` and `runtime` are also
|
|
1318
|
+
usable as libraries.
|
|
1319
|
+
- **Release**: the `npm` job of `release.yml` sets the version of the tag on the four packages,
|
|
1320
|
+
builds them, packs them (`pnpm pack`), installs the archives with npm in an empty folder and
|
|
1321
|
+
runs `rapt --help`, `rapt check` and `rapt build` of the fixture; then, when the secret
|
|
1322
|
+
`NPM_TOKEN` is set, publishes them (`workspace:*` becomes the version of the tag). npm
|
|
1323
|
+
provenance is added when the repository is public. Once: create the `open-rap-runtime`
|
|
1324
|
+
organization on npmjs.com and the `NPM_TOKEN` secret ([DEPLOYMENT.md](DEPLOYMENT.md)). The name
|
|
1325
|
+
`rapt` is taken on npm, hence `open-rap-runtime`.
|
|
1326
|
+
- **Why**: the same CLI everywhere; CI can check RAP sources without VS Code; a terminal agent
|
|
1327
|
+
can install it in one command.
|
|
1328
|
+
- **Note**: publishing makes the compiled code public on npm, as the `.vsix` of the website
|
|
1329
|
+
already does.
|
|
1330
|
+
- **Tests**: `cli/test/package.test.ts` (dependencies, a single run), `cli/test/cli.test.ts`
|
|
1331
|
+
(`rapt check`, `rapt agent` in the help), `compiler/test/cli.test.ts` (`isEntryPoint`); the four
|
|
1332
|
+
packages packed and installed with npm in an empty folder: `rapt check`, `rapt dev` (UI,
|
|
1333
|
+
OpenUI5, OData, documentation) work; `pnpm publish --dry-run`.
|
|
1334
|
+
|
|
1335
|
+
## D66 — Menus of the extension: two ways to create, greyed-out commands, required floorplan
|
|
1336
|
+
|
|
1337
|
+
- **Context**: the right click of the explorer showed three creation entries (*New business
|
|
1338
|
+
object*, *New RAP object (wizard)…*, *New RAP object… (table, view…)*), all the commands were
|
|
1339
|
+
active even outside a project, and the application was generated as a List Report without the
|
|
1340
|
+
floorplan having been chosen.
|
|
1341
|
+
- **Decision**:
|
|
1342
|
+
- **Create**: two entries. *New complete business object…* (`rapt new`, possible in an empty
|
|
1343
|
+
folder) builds the whole BO; *New RAP object…* (the wizard, D61) creates object by object.
|
|
1344
|
+
The quick picks of D46 (`rapt.generate`) stay a command, outside the menus and the palette.
|
|
1345
|
+
- **Groups** of the menu: create, project (check, abapGit), application (floorplan, rapt dev,
|
|
1346
|
+
build); the *RAP Objects* view offers the same on a project, and a *New complete business
|
|
1347
|
+
object* button when the workspace has no project.
|
|
1348
|
+
- **Greyed out**: the extension computes the folders of the projects (`rapt.projects`) and
|
|
1349
|
+
those of the projects that have a service definition (`rapt.apps`) from the sources of the
|
|
1350
|
+
workspace, updated when a file is created or deleted. The commands have an `enablement`:
|
|
1351
|
+
`resourcePath in rapt.projects` (or `rapt.apps`) in the explorer, a project (or a service) in
|
|
1352
|
+
the workspace elsewhere; *Stop* and *Open the application* when rapt dev runs.
|
|
1353
|
+
- **Required floorplan**: *Build the application…* and *Start rapt dev* ask for the floorplan
|
|
1354
|
+
of each root entity set that has none; cancelling writes nothing and builds nothing.
|
|
1355
|
+
`rapt floorplan --json` gives `chosen` per entity set, and `rapt floorplan . Set listReport`
|
|
1356
|
+
writes the List Report in `rapt/config.json` (instead of removing it) to keep the choice. The
|
|
1357
|
+
CLI keeps the List Report as the default.
|
|
1358
|
+
- **Why**: one way to do each gesture, an impossible command is not offered, and the floorplan is
|
|
1359
|
+
a design choice of the application, not a default one suffers.
|
|
1360
|
+
- **Tests**: `vscode/test/rapt.test.ts` (folders of the projects and of the applications,
|
|
1361
|
+
`chosen`, List Report kept).
|
|
1362
|
+
|
|
1363
|
+
## D67 — Expressions in the elements of the CDS views
|
|
1364
|
+
|
|
1365
|
+
- **Context**: a calculated element of the select list (`CASE`, `CAST`, calculation, function,
|
|
1366
|
+
literal) was refused (CDS002), while RAP views often have some (status text, total amount,
|
|
1367
|
+
duration).
|
|
1368
|
+
- **Decision**: the adapter turns the expression parsed by abaplint into a tree; the IR resolves
|
|
1369
|
+
and types it (`FieldModel.expression` instead of `column`); the SQL generator translates it to
|
|
1370
|
+
PostgreSQL in the view (`CAST(… AS type)` around the result, for a stable column type).
|
|
1371
|
+
- **Supported**: literals (string, integer, decimal, `abap.xxx'…'`), columns of the source,
|
|
1372
|
+
`$projection.Element`, `+ - * /` and parentheses, simple and searched `CASE`
|
|
1373
|
+
(`= <> < > <= >=`, `[NOT] LIKE`, `IS [NOT] NULL`, `AND`, `OR`, `NOT`), `CAST( x AS abap.xxx )`
|
|
1374
|
+
or to a data element, functions `concat`, `concat_with_space`, `substring`, `upper`, `lower`,
|
|
1375
|
+
`length`, `left`, `right`, `ltrim`, `rtrim`, `replace`, `coalesce`, `div`, `division`, `mod`,
|
|
1376
|
+
`abs`, `round`, `ceil`, `floor`, `dats_days_between`, `dats_add_days`, and
|
|
1377
|
+
`$session.system_date` (`current_date`). A projection takes the calculated elements of its
|
|
1378
|
+
view and accepts expressions itself on the elements of the projected entity.
|
|
1379
|
+
- **Typing**: the explicit `CAST` wins; otherwise, calculation on numbers (integers: the
|
|
1380
|
+
largest; decimals: precision and scale combined, 31 digits at most; `/`: `decfloat34`),
|
|
1381
|
+
string for `concat` (lengths added), common type of the branches of a `CASE`. When the type
|
|
1382
|
+
cannot be derived (`NULL` alone, `+` on a string), CDS022 asks for a `CAST`.
|
|
1383
|
+
- **ABAP semantics**: `CHAR` strings lose their trailing blanks in `concat` and `length`; `div`
|
|
1384
|
+
is an integer division, `division( a, b, d )` rounds to `d` decimals; `/` gives a decimal (no
|
|
1385
|
+
integer division of PostgreSQL).
|
|
1386
|
+
- **Read-only**: a calculated element cannot be a key (CDS022); it is neither in the mapping of
|
|
1387
|
+
the BDEF, nor in the draft table, nor in the writes. In OData, it is `Core.Computed` and
|
|
1388
|
+
ignored in the body of the requests (OData convention of computed properties).
|
|
1389
|
+
- **Drafts**: the draft view computes the expression on the values of the draft; a column of
|
|
1390
|
+
the source that the entity does not expose is `NULL` there. In the behavior pool (EML), the
|
|
1391
|
+
value read is the one of the active view: it reflects the last save, not the current changes
|
|
1392
|
+
of the transaction; a draft instance has the initial value there.
|
|
1393
|
+
- **Still CDS002** (message naming the construct): `$session.user`, `$session.client`,
|
|
1394
|
+
`$session.system_language` (the SQL views do not have the session of the request),
|
|
1395
|
+
association paths (`_Assoc.field`), associations in an expression, `BETWEEN`, aggregates, SQL
|
|
1396
|
+
functions not listed, parameters and path filters. Joins and `WHERE` unchanged.
|
|
1397
|
+
- **Why**: PostgreSQL computes on read, like the HANA database: `$filter` and `$orderby` on a
|
|
1398
|
+
calculated element work without code at runtime, and the value stays consistent with the data.
|
|
1399
|
+
- **Tests**: tree of the adapter, inferred types and diagnostics (CDS002, CDS004, CDS022), schema
|
|
1400
|
+
applied on PGlite and values read in the view and the projection, `$metadata`
|
|
1401
|
+
(`Core.Computed`), OData read, `PATCH` ignored and values computed on a draft then after
|
|
1402
|
+
activation.
|
|
1403
|
+
|
|
1404
|
+
## D68 — Filter value help: *Search and Select* and *Define Conditions*
|
|
1405
|
+
|
|
1406
|
+
- **Context**: the filter fields of the List Report (and of the ALP) accepted typed conditions
|
|
1407
|
+
(D51) and opened, for a field with a value help, a list of values (D44); the dialog of SAP
|
|
1408
|
+
Fiori elements where conditions are composed (operator, values, exclusions) next to the
|
|
1409
|
+
selection in the value help was missing. The dialog of `sap.ui.comp` / `sap.ui.mdc` is SAPUI5
|
|
1410
|
+
only.
|
|
1411
|
+
- **Decision**:
|
|
1412
|
+
- the icon of each token filter field (all but booleans and enumerated types, which stay choice
|
|
1413
|
+
lists) opens a dialog built with `sap.m` (`controller/conditions.js`): a *Search and Select*
|
|
1414
|
+
tab (when the field has a value help: search, table with multiple or single selection,
|
|
1415
|
+
descriptions of the text view), a *Define Conditions* tab (one row per condition: operator,
|
|
1416
|
+
value(s), value help on the value, *Add*), and at the bottom the *Selected Items and
|
|
1417
|
+
Conditions* tokens;
|
|
1418
|
+
- operators as in Fiori elements: *equal to*, *contains*, *starts with*, *ends with*,
|
|
1419
|
+
*between*, *less than*, *less than or equal to*, *greater than*, *greater than or equal to*,
|
|
1420
|
+
*empty*, and their exclusions (*not equal to*, *does not contain*, *not between*, *not
|
|
1421
|
+
empty*…), filtered by type: text all of them, numbers and NUMC comparisons, dates and times
|
|
1422
|
+
comparisons and *empty* (date / time picker), UUID equal / not equal;
|
|
1423
|
+
- `$filter`: included conditions of a field with `or`, exclusions with `and not`, fields with
|
|
1424
|
+
`and`; *empty* of a text = `''` or `null`; date-time by whole days (UTC);
|
|
1425
|
+
- tokens written as in Fiori elements: value alone (with its description: "Euro (EUR)"),
|
|
1426
|
+
`!(=x)`, `*x*`, `a...b`, `<empty>`, `!(…)`; the key of the token is the condition (`=x`,
|
|
1427
|
+
`!(*x*)`) read again for the search and kept in the variants (earlier variants are still
|
|
1428
|
+
read, including the date ranges `{ from, to }`);
|
|
1429
|
+
- direct input: `!(…)`, `<empty>` and `a - b` (dates) in addition to D51; a single value is
|
|
1430
|
+
*contains* for a free text, *equal to* for a key or a field with a value help; dates are
|
|
1431
|
+
entered in the format of the language or in ISO;
|
|
1432
|
+
- `@Consumption.filter.selectionType` (`AllowedExpressions` already in the CSDL, D57):
|
|
1433
|
+
`#SINGLE` equal only, `#INTERVAL` comparisons and ranges (without patterns), a single
|
|
1434
|
+
condition when `multipleSelections: false` (without exclusions for `SingleRange`).
|
|
1435
|
+
- **Why**: it is the "match code" of the Fiori elements filters; keeping the selection in the
|
|
1436
|
+
value help and the conditions in the same dialog avoids two different gestures for one field.
|
|
1437
|
+
- **Limits**: no relative date operators (*today*, *last week*…); booleans and enumerated types
|
|
1438
|
+
keep their choice list; a value entered without a value help is shown without a description.
|
|
1439
|
+
- **Tests**: `compiler/test/ui.test.ts` (reading and writing of the conditions, operators by type
|
|
1440
|
+
and `selectionType`, `$filter`), `runtime/test/table-entity.test.ts` (dialog in Chromium:
|
|
1441
|
+
selection in the value help, exclusion, typed conditions, filtered rows).
|
|
1442
|
+
|
|
1443
|
+
## D69 — One launchpad per workspace, applications added when they are created
|
|
1444
|
+
|
|
1445
|
+
- **Context**: `launchpad.json` was written by hand (D37) and only referenced applications
|
|
1446
|
+
already built (`build`). In a workspace of several applications (`rap-apps/helpdesk-tickets`,
|
|
1447
|
+
`rap-apps/project-tracker`…), you had to write it, build each application then the launchpad,
|
|
1448
|
+
and nothing said that only one was needed.
|
|
1449
|
+
- **Decision**:
|
|
1450
|
+
- **One launchpad per workspace**, titled "Launchpad". The CLI looks for `launchpad.json` in
|
|
1451
|
+
the folder of the project then its parents (4 levels, stopping after a folder that contains
|
|
1452
|
+
`.git`, never in the root of the disk, the home folder or the temporary folder); without it,
|
|
1453
|
+
it creates it in the **parent folder of the project** (the workspace), or in the project when
|
|
1454
|
+
it is the root of a git repository or when its parent is one of these system folders.
|
|
1455
|
+
`--launchpad <file>` designates another file. The extension always passes `--launchpad`: next
|
|
1456
|
+
to the saved `.code-workspace`, otherwise in the **first folder** of the workspace — a single
|
|
1457
|
+
launchpad, even for a multi-root workspace.
|
|
1458
|
+
- **Added on creation**: `rapt new` (complete BO) and `rapt floorplan <src> <Set> <floorplan>`
|
|
1459
|
+
(choosing the floorplan creates the Fiori application) add the application; `--no-launchpad`
|
|
1460
|
+
refrains from it; `rapt launchpad add <src>` adds it on demand (`--json`). The `apps` entry
|
|
1461
|
+
references the **sources** (`"source": "helpdesk-tickets"`, relative to the launchpad.json),
|
|
1462
|
+
its `id` comes from the name of the folder, its `url` (local start) takes the first free port
|
|
1463
|
+
from 8081. The tile goes into the "Applications" group (or the "Applications" section of the
|
|
1464
|
+
first page of the first space): title of the application, intent `<EntitySet>-manage`
|
|
1465
|
+
(`process` for a Worklist, `analyze` for an Analytical List Page, `create` for a Form Entry),
|
|
1466
|
+
icon and counter of the starting entity set. **Idempotent**: an application already present
|
|
1467
|
+
(same sources, or same id without sources) is not changed, nothing the user wrote is
|
|
1468
|
+
rewritten; an unreadable file is not overwritten.
|
|
1469
|
+
- **Build**: `rapt launchpad launchpad.json --out out/launchpad` also builds each application
|
|
1470
|
+
referenced by `source` in `out/launchpad/apps/<id>`; `docker-compose.yml` builds them from
|
|
1471
|
+
this folder (one PostgreSQL each) and the image of the launchpad ignores it. `source` and
|
|
1472
|
+
`build` together are refused (LPD009); `build` stays possible for an application built
|
|
1473
|
+
separately.
|
|
1474
|
+
- **Two deployment modes**: the **launchpad with all its applications** (`rapt launchpad`), or
|
|
1475
|
+
**one application alone** (`rapt build`), which does not depend on the launchpad: its shell
|
|
1476
|
+
bar only shows if `/launchpad/config` answers. The extension asks for the mode in *Build the
|
|
1477
|
+
application…* ("Launchpad (all the applications)" / "Application alone (standalone)");
|
|
1478
|
+
*Build the launchpad…* builds the one of the workspace (`rapt.hasLaunchpad`). `rapt dev` keeps
|
|
1479
|
+
its development launchpad (D49).
|
|
1480
|
+
- **Why**: the user creates applications, not configuration files; a single entry point per
|
|
1481
|
+
workspace, like the Fiori Launchpad of a system, and the choice of shipping one application
|
|
1482
|
+
alone stays open.
|
|
1483
|
+
- **Tests**: `compiler/test/launchpad.test.ts` (creation titled "Launchpad", idempotent addition
|
|
1484
|
+
without rewriting a changed tile, two projects of one folder in the same launchpad, `rapt new`,
|
|
1485
|
+
`--no-launchpad`, `--launchpad`, build of the applications with the launchpad, LPD009);
|
|
1486
|
+
`vscode/test/rapt.test.ts` (file of the workspace, `rapt floorplan` and `rapt launchpad add` in
|
|
1487
|
+
JSON).
|
|
1488
|
+
|
|
1489
|
+
## D70 — Name: Open RAP Runtime
|
|
1490
|
+
|
|
1491
|
+
- **Context**: "rap-transpiler" described one step of the tool (abaplint transpiles the ABAP);
|
|
1492
|
+
the project is a runtime for RAP business objects outside SAP (OData V4, RAP managed runtime,
|
|
1493
|
+
UI, launchpad, MCP), with a compiler and a VS Code extension around it.
|
|
1494
|
+
- **Decision**: the project is named **Open RAP Runtime**. The npm package of the command line is
|
|
1495
|
+
`open-rap-runtime`, the libraries `@open-rap/model`, `@open-rap/compiler`, `@open-rap/runtime`
|
|
1496
|
+
(npm organization `open-rap`), the VS Code extension `open-rap-runtime-vscode` ("Open RAP
|
|
1497
|
+
Runtime"). Unchanged, so that existing projects keep working: the `rapt` command, the `rapt/`
|
|
1498
|
+
folder of the projects, the `.rapt/` build folder, the `rapt_*` tables, the `ZRAP_*` ABAP
|
|
1499
|
+
objects. The URL of the website follows the name of the GitHub repository
|
|
1500
|
+
(`RAP-Transpiler`) until it is renamed.
|
|
1501
|
+
- **Why**: "Open" says open source and without proprietary SAP libraries; "RAP Runtime" says
|
|
1502
|
+
what the application runs. The website states that the project is not affiliated with SAP SE.
|
|
1503
|
+
- **Tests**: the whole suite (package names, generated applications with `vendor/open-rap-*`,
|
|
1504
|
+
extension), the `.vsix` built and its `rapt dev` checked.
|
|
1505
|
+
|
|
1506
|
+
## D71 — Feedback: a public feedback repository and a form on the website
|
|
1507
|
+
|
|
1508
|
+
- **Context**: the repository is private, so its issues are not open to the users; bug reports
|
|
1509
|
+
and feature requests had no place.
|
|
1510
|
+
- **Decision**: a public repository, `ClementRingot/open-rap-runtime-feedback`, holds the issues,
|
|
1511
|
+
with GitHub issue forms (`bug_report.yml`: component, description, steps, logs, version,
|
|
1512
|
+
environment; `feature_request.yml`: area, description, why, example, version; blank issues
|
|
1513
|
+
disabled). The website has a `feedback.html` page: a form (type, component, title,
|
|
1514
|
+
description, steps or use case, version, environment) that opens a new issue of the right form
|
|
1515
|
+
with its fields filled in (GitHub fills an issue form field from the query parameter of its
|
|
1516
|
+
id); the author reviews it and submits it on GitHub. The page reads `kind`, `version`,
|
|
1517
|
+
`environment`, `component`, `title` and `description` from its own query string: the VS Code
|
|
1518
|
+
command *RAP: Report a bug or request a feature…* opens it with the version of the extension
|
|
1519
|
+
and the environment (VS Code, operating system). The help of `rapt` and the footer of every page
|
|
1520
|
+
of the website link to it.
|
|
1521
|
+
- **Why**: no server to run (a static page and GitHub), issues that everyone can follow and
|
|
1522
|
+
answer, and reports that come with the version and the environment.
|
|
1523
|
+
- **Tests**: `vscode/test/rapt.test.ts` (URL of the form); the page checked in Chromium (required
|
|
1524
|
+
fields, prefill from the query string, URL of the issue with the fields of the bug form,
|
|
1525
|
+
fields of the feature form, mobile without horizontal scrolling).
|
|
1526
|
+
|