@open-rap/compiler 0.0.0-stage → 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (227) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +21 -2
  3. package/dist/agent/server.d.ts +35 -0
  4. package/dist/agent/server.js +408 -0
  5. package/dist/agent/server.js.map +1 -0
  6. package/dist/authoring/abapgit.d.ts +38 -0
  7. package/dist/authoring/abapgit.js +242 -0
  8. package/dist/authoring/abapgit.js.map +1 -0
  9. package/dist/authoring/catalog.d.ts +48 -0
  10. package/dist/authoring/catalog.js +117 -0
  11. package/dist/authoring/catalog.js.map +1 -0
  12. package/dist/authoring/floorplan.d.ts +28 -0
  13. package/dist/authoring/floorplan.js +31 -0
  14. package/dist/authoring/floorplan.js.map +1 -0
  15. package/dist/authoring/generate.d.ts +57 -0
  16. package/dist/authoring/generate.js +630 -0
  17. package/dist/authoring/generate.js.map +1 -0
  18. package/dist/authoring/mcp.d.ts +15 -0
  19. package/dist/authoring/mcp.js +52 -0
  20. package/dist/authoring/mcp.js.map +1 -0
  21. package/dist/authoring/new.d.ts +35 -0
  22. package/dist/authoring/new.js +389 -0
  23. package/dist/authoring/new.js.map +1 -0
  24. package/dist/bdef/ast.d.ts +249 -0
  25. package/dist/bdef/ast.js +3 -0
  26. package/dist/bdef/ast.js.map +1 -0
  27. package/dist/bdef/lexer.d.ts +105 -0
  28. package/dist/bdef/lexer.js +148 -0
  29. package/dist/bdef/lexer.js.map +1 -0
  30. package/dist/bdef/parser.d.ts +22 -0
  31. package/dist/bdef/parser.js +1012 -0
  32. package/dist/bdef/parser.js.map +1 -0
  33. package/dist/build.d.ts +26 -0
  34. package/dist/build.js +62 -0
  35. package/dist/build.js.map +1 -0
  36. package/dist/cds/adapter.d.ts +91 -0
  37. package/dist/cds/adapter.js +381 -0
  38. package/dist/cds/adapter.js.map +1 -0
  39. package/dist/cds/expression-ast.d.ts +65 -0
  40. package/dist/cds/expression-ast.js +330 -0
  41. package/dist/cds/expression-ast.js.map +1 -0
  42. package/dist/cli.d.ts +9 -0
  43. package/dist/cli.js +485 -0
  44. package/dist/cli.js.map +1 -0
  45. package/dist/dcl/parser.d.ts +59 -0
  46. package/dist/dcl/parser.js +338 -0
  47. package/dist/dcl/parser.js.map +1 -0
  48. package/dist/ddic/catalog.d.ts +17 -0
  49. package/dist/ddic/catalog.js +211 -0
  50. package/dist/ddic/catalog.js.map +1 -0
  51. package/dist/ddic/cds-types.d.ts +47 -0
  52. package/dist/ddic/cds-types.js +131 -0
  53. package/dist/ddic/cds-types.js.map +1 -0
  54. package/dist/ddic/table-source.d.ts +70 -0
  55. package/dist/ddic/table-source.js +500 -0
  56. package/dist/ddic/table-source.js.map +1 -0
  57. package/dist/ddic/xml.d.ts +65 -0
  58. package/dist/ddic/xml.js +201 -0
  59. package/dist/ddic/xml.js.map +1 -0
  60. package/dist/dev.d.ts +128 -0
  61. package/dist/dev.js +434 -0
  62. package/dist/dev.js.map +1 -0
  63. package/dist/diagnostics.d.ts +33 -0
  64. package/dist/diagnostics.js +53 -0
  65. package/dist/diagnostics.js.map +1 -0
  66. package/dist/gen/abap/derived-types.d.ts +26 -0
  67. package/dist/gen/abap/derived-types.js +323 -0
  68. package/dist/gen/abap/derived-types.js.map +1 -0
  69. package/dist/gen/abap/eml.d.ts +118 -0
  70. package/dist/gen/abap/eml.js +362 -0
  71. package/dist/gen/abap/eml.js.map +1 -0
  72. package/dist/gen/abap/enums.d.ts +13 -0
  73. package/dist/gen/abap/enums.js +74 -0
  74. package/dist/gen/abap/enums.js.map +1 -0
  75. package/dist/gen/abap/framework-http.d.ts +14 -0
  76. package/dist/gen/abap/framework-http.js +1049 -0
  77. package/dist/gen/abap/framework-http.js.map +1 -0
  78. package/dist/gen/abap/framework-query.d.ts +14 -0
  79. package/dist/gen/abap/framework-query.js +225 -0
  80. package/dist/gen/abap/framework-query.js.map +1 -0
  81. package/dist/gen/abap/framework-utils.d.ts +29 -0
  82. package/dist/gen/abap/framework-utils.js +3707 -0
  83. package/dist/gen/abap/framework-utils.js.map +1 -0
  84. package/dist/gen/abap/framework.d.ts +12 -0
  85. package/dist/gen/abap/framework.js +480 -0
  86. package/dist/gen/abap/framework.js.map +1 -0
  87. package/dist/gen/abap/generate.d.ts +40 -0
  88. package/dist/gen/abap/generate.js +238 -0
  89. package/dist/gen/abap/generate.js.map +1 -0
  90. package/dist/gen/abap/rewrite.d.ts +82 -0
  91. package/dist/gen/abap/rewrite.js +957 -0
  92. package/dist/gen/abap/rewrite.js.map +1 -0
  93. package/dist/gen/abap/shadow.d.ts +25 -0
  94. package/dist/gen/abap/shadow.js +175 -0
  95. package/dist/gen/abap/shadow.js.map +1 -0
  96. package/dist/gen/abap/tokens.d.ts +33 -0
  97. package/dist/gen/abap/tokens.js +87 -0
  98. package/dist/gen/abap/tokens.js.map +1 -0
  99. package/dist/gen/app/index.d.ts +25 -0
  100. package/dist/gen/app/index.js +322 -0
  101. package/dist/gen/app/index.js.map +1 -0
  102. package/dist/gen/csdl.d.ts +11 -0
  103. package/dist/gen/csdl.js +562 -0
  104. package/dist/gen/csdl.js.map +1 -0
  105. package/dist/gen/deploy.d.ts +12 -0
  106. package/dist/gen/deploy.js +53 -0
  107. package/dist/gen/deploy.js.map +1 -0
  108. package/dist/gen/docs/index.d.ts +35 -0
  109. package/dist/gen/docs/index.js +267 -0
  110. package/dist/gen/docs/index.js.map +1 -0
  111. package/dist/gen/docs/markdown.d.ts +23 -0
  112. package/dist/gen/docs/markdown.js +187 -0
  113. package/dist/gen/docs/markdown.js.map +1 -0
  114. package/dist/gen/docs/site.d.ts +17 -0
  115. package/dist/gen/docs/site.js +106 -0
  116. package/dist/gen/docs/site.js.map +1 -0
  117. package/dist/gen/keycloak.d.ts +21 -0
  118. package/dist/gen/keycloak.js +90 -0
  119. package/dist/gen/keycloak.js.map +1 -0
  120. package/dist/gen/launchpad/index.d.ts +48 -0
  121. package/dist/gen/launchpad/index.js +302 -0
  122. package/dist/gen/launchpad/index.js.map +1 -0
  123. package/dist/gen/launchpad/ui.d.ts +14 -0
  124. package/dist/gen/launchpad/ui.js +350 -0
  125. package/dist/gen/launchpad/ui.js.map +1 -0
  126. package/dist/gen/mcp.d.ts +23 -0
  127. package/dist/gen/mcp.js +244 -0
  128. package/dist/gen/mcp.js.map +1 -0
  129. package/dist/gen/sql-expressions.d.ts +12 -0
  130. package/dist/gen/sql-expressions.js +136 -0
  131. package/dist/gen/sql-expressions.js.map +1 -0
  132. package/dist/gen/sql.d.ts +38 -0
  133. package/dist/gen/sql.js +612 -0
  134. package/dist/gen/sql.js.map +1 -0
  135. package/dist/gen/ui/index.d.ts +7 -0
  136. package/dist/gen/ui/index.js +27 -0
  137. package/dist/gen/ui/index.js.map +1 -0
  138. package/dist/gen/ui/layout.d.ts +230 -0
  139. package/dist/gen/ui/layout.js +511 -0
  140. package/dist/gen/ui/layout.js.map +1 -0
  141. package/dist/gen/ui/static.d.ts +55 -0
  142. package/dist/gen/ui/static.js +2901 -0
  143. package/dist/gen/ui/static.js.map +1 -0
  144. package/dist/gen/ui/views.d.ts +10 -0
  145. package/dist/gen/ui/views.js +511 -0
  146. package/dist/gen/ui/views.js.map +1 -0
  147. package/dist/gen/vendor.d.ts +23 -0
  148. package/dist/gen/vendor.js +68 -0
  149. package/dist/gen/vendor.js.map +1 -0
  150. package/dist/gen/xml.d.ts +9 -0
  151. package/dist/gen/xml.js +42 -0
  152. package/dist/gen/xml.js.map +1 -0
  153. package/dist/index.d.ts +20 -0
  154. package/dist/index.js +20 -0
  155. package/dist/index.js.map +1 -0
  156. package/dist/input/project.d.ts +50 -0
  157. package/dist/input/project.js +128 -0
  158. package/dist/input/project.js.map +1 -0
  159. package/dist/ir/access-controls.d.ts +11 -0
  160. package/dist/ir/access-controls.js +148 -0
  161. package/dist/ir/access-controls.js.map +1 -0
  162. package/dist/ir/authorizations.d.ts +25 -0
  163. package/dist/ir/authorizations.js +178 -0
  164. package/dist/ir/authorizations.js.map +1 -0
  165. package/dist/ir/behaviors.d.ts +16 -0
  166. package/dist/ir/behaviors.js +548 -0
  167. package/dist/ir/behaviors.js.map +1 -0
  168. package/dist/ir/builder.d.ts +20 -0
  169. package/dist/ir/builder.js +505 -0
  170. package/dist/ir/builder.js.map +1 -0
  171. package/dist/ir/config.d.ts +25 -0
  172. package/dist/ir/config.js +130 -0
  173. package/dist/ir/config.js.map +1 -0
  174. package/dist/ir/dictionary.d.ts +54 -0
  175. package/dist/ir/dictionary.js +328 -0
  176. package/dist/ir/dictionary.js.map +1 -0
  177. package/dist/ir/entities.d.ts +58 -0
  178. package/dist/ir/entities.js +676 -0
  179. package/dist/ir/entities.js.map +1 -0
  180. package/dist/ir/expressions.d.ts +33 -0
  181. package/dist/ir/expressions.js +327 -0
  182. package/dist/ir/expressions.js.map +1 -0
  183. package/dist/ir/handlers.d.ts +63 -0
  184. package/dist/ir/handlers.js +398 -0
  185. package/dist/ir/handlers.js.map +1 -0
  186. package/dist/ir/number-ranges.d.ts +24 -0
  187. package/dist/ir/number-ranges.js +74 -0
  188. package/dist/ir/number-ranges.js.map +1 -0
  189. package/dist/ir/seed.d.ts +23 -0
  190. package/dist/ir/seed.js +78 -0
  191. package/dist/ir/seed.js.map +1 -0
  192. package/dist/ir/service.d.ts +21 -0
  193. package/dist/ir/service.js +96 -0
  194. package/dist/ir/service.js.map +1 -0
  195. package/dist/launchpad/config.d.ts +47 -0
  196. package/dist/launchpad/config.js +321 -0
  197. package/dist/launchpad/config.js.map +1 -0
  198. package/dist/launchpad/workspace.d.ts +48 -0
  199. package/dist/launchpad/workspace.js +166 -0
  200. package/dist/launchpad/workspace.js.map +1 -0
  201. package/dist/srvd/parser.d.ts +36 -0
  202. package/dist/srvd/parser.js +193 -0
  203. package/dist/srvd/parser.js.map +1 -0
  204. package/dist/standard/data.d.ts +18 -0
  205. package/dist/standard/data.js +534 -0
  206. package/dist/standard/data.js.map +1 -0
  207. package/dist/standard/index.d.ts +36 -0
  208. package/dist/standard/index.js +507 -0
  209. package/dist/standard/index.js.map +1 -0
  210. package/dist/version.d.ts +2 -0
  211. package/dist/version.js +4 -0
  212. package/dist/version.js.map +1 -0
  213. package/docs/AGENTS.md +79 -0
  214. package/docs/ANNOTATIONS.md +76 -0
  215. package/docs/DECISIONS.md +1526 -0
  216. package/docs/DEPLOYMENT.md +113 -0
  217. package/docs/DIAGNOSTICS.md +116 -0
  218. package/docs/FEATURES.md +287 -0
  219. package/docs/GUIDE.md +560 -0
  220. package/docs/KNOWN_GAPS.md +216 -0
  221. package/docs/MCP.md +84 -0
  222. package/docs/PLAN.md +690 -0
  223. package/docs/README.md +148 -0
  224. package/docs/REMOTE_APIS.md +121 -0
  225. package/docs/ROADMAP.md +35 -0
  226. package/docs/UTILITIES.md +95 -0
  227. package/package.json +49 -3
@@ -0,0 +1,630 @@
1
+ /**
2
+ * `rapt generate <src> <kind> <name>` (decision D46): one new object with the file names abapGit
3
+ * expects, like the creation wizards of the ABAP Development Tools. A skeleton when there is
4
+ * nothing to start from; derived from an existing object otherwise (`--from`): a view entity from
5
+ * its table, a projection from its view, a metadata extension, an access control or a behavior
6
+ * definition from an entity, a behavior pool from its behavior definition. The abapGit XML is
7
+ * written too (`rapt abapgit`). An existing file is never overwritten.
8
+ */
9
+ import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
10
+ import path from "node:path";
11
+ import { getAnnotation, isUuid } from "@open-rap/model";
12
+ import { parseBuiltInType } from "../ddic/catalog.js";
13
+ import { parseBdef } from "../bdef/parser.js";
14
+ import { abapGitXml, xmlElement } from "../ddic/table-source.js";
15
+ import { Diagnostics } from "../diagnostics.js";
16
+ import { filesOfType, loadProject } from "../input/project.js";
17
+ import { Dictionary } from "../ir/dictionary.js";
18
+ import { EntityResolver } from "../ir/entities.js";
19
+ import { generateAbapGitFiles } from "./abapgit.js";
20
+ export const OBJECT_KINDS = {
21
+ table: "database table (.tabl.astabl)",
22
+ view: "CDS view entity, from a table with --from (.ddls.asddls)",
23
+ projection: "CDS projection view, from a view with --from (.ddls.asddls)",
24
+ abstract: "CDS abstract entity, e.g. an action parameter (.ddls.asddls)",
25
+ custom: "CDS custom entity and its query class (.ddls.asddls, .clas.abap)",
26
+ tableentity: "CDS table entity: a table and its entity, the persistence of a BO (.ddls.asddls)",
27
+ type: "CDS simple type (.drty.acds)",
28
+ dataelement: "data element, of a built-in type (--type abap.char(10)) or a domain (--from) (.dtel.xml)",
29
+ domain: "domain of a built-in type (--type abap.char(1)) (.doma.xml)",
30
+ enum: "CDS enumerated type (.drty.acds)",
31
+ metadata: "metadata extension (UI annotations) of an entity (.ddlx.asddlxs)",
32
+ access: "access control (DCL) of an entity (.dcls.asdcls)",
33
+ behavior: "behavior definition of a root view or projection (.bdef.asbdef)",
34
+ pool: "behavior pool of a behavior definition, with its handlers (.clas.abap, .clas.locals_imp.abap)",
35
+ class: "ABAP class (.clas.abap)",
36
+ interface: "ABAP interface (.intf.abap)",
37
+ service: "service definition, exposing --expose A,B (.srvd.srvdsrv)",
38
+ binding: "OData V4 UI service binding of a service definition --for (.srvb.xml)",
39
+ };
40
+ const NAME = /^[A-Za-z][A-Za-z0-9_]*$/;
41
+ function sourceFolder(root) {
42
+ const dotAbapGit = path.join(root, ".abapgit.xml");
43
+ const folder = existsSync(dotAbapGit) ? (/<STARTING_FOLDER>([^<]*)<\/STARTING_FOLDER>/.exec(readFileSync(dotAbapGit, "utf8"))?.[1] ?? "/src/") : "/src/";
44
+ return path.join(root, folder.replace(/^\/+/, ""));
45
+ }
46
+ /** `travel_uuid` → `TravelUUID`, `agency_id` → `AgencyID`, `begin_date` → `BeginDate`. */
47
+ export function elementName(column) {
48
+ return column
49
+ .toLowerCase()
50
+ .split("_")
51
+ .filter((p) => p !== "")
52
+ .map((p) => (p === "uuid" || p === "id" ? p.toUpperCase() : p.charAt(0).toUpperCase() + p.slice(1)))
53
+ .join("");
54
+ }
55
+ /** `ZR_Travel` → `Travel`, `ZRT_C_Booking` → `Booking`: the alias of an entity in a BDEF. */
56
+ export function aliasOf(entity) {
57
+ const parts = entity.split("_");
58
+ const last = parts[parts.length - 1] ?? entity;
59
+ return last.charAt(0).toUpperCase() + last.slice(1);
60
+ }
61
+ /** The behavior pool of a BDEF, as SAP names it: `ZR_TRAVEL` → `ZBP_R_TRAVEL`. */
62
+ export function poolName(bdef) {
63
+ const upper = bdef.toUpperCase();
64
+ const rest = upper.replace(/^[ZY]_?/, "");
65
+ return `${upper.charAt(0) === "Y" ? "Y" : "Z"}BP_${rest}`.slice(0, 30);
66
+ }
67
+ const ADMIN = {
68
+ ABP_CREATION_USER: "@Semantics.user.createdBy: true",
69
+ ABP_CREATION_TSTMPL: "@Semantics.systemDateTime.createdAt: true",
70
+ ABP_LASTCHANGE_USER: "@Semantics.user.lastChangedBy: true",
71
+ ABP_LASTCHANGE_TSTMPL: "@Semantics.systemDateTime.lastChangedAt: true",
72
+ ABP_LOCINST_LASTCHANGE_TSTMPL: "@Semantics.systemDateTime.localInstanceLastChangedAt: true",
73
+ };
74
+ const label = (text) => text.replace(/([a-z0-9])([A-Z])/g, "$1 $2").replace(/'/g, "''");
75
+ function context(root) {
76
+ const project = loadProject(root);
77
+ const diagnostics = new Diagnostics();
78
+ const dictionary = new Dictionary(project, diagnostics);
79
+ return { project, dictionary, entities: new EntityResolver(project, dictionary, diagnostics) };
80
+ }
81
+ function entity(ctx, name, what) {
82
+ if (name === undefined) {
83
+ return `${what}: give the entity with --from`;
84
+ }
85
+ return ctx.entities.resolve(name) ?? `${name}: no CDS entity of that name in the sources`;
86
+ }
87
+ // ------------------------------------------------------------------------------------- templates
88
+ function tableSource(name, description) {
89
+ return `@EndUserText.label : '${description}'
90
+ @AbapCatalog.enhancement.category : #NOT_EXTENSIBLE
91
+ @AbapCatalog.tableCategory : #TRANSPARENT
92
+ @AbapCatalog.deliveryClass : #A
93
+ @AbapCatalog.dataMaintenance : #RESTRICTED
94
+ define table ${name} {
95
+ key client : abap.clnt not null;
96
+ key uuid : sysuuid_x16 not null;
97
+ description : abap.char(256);
98
+ created_by : abp_creation_user;
99
+ created_at : abp_creation_tstmpl;
100
+ last_changed_by : abp_lastchange_user;
101
+ last_changed_at : abp_lastchange_tstmpl;
102
+ local_last_changed_at : abp_locinst_lastchange_tstmpl;
103
+ }
104
+ `;
105
+ }
106
+ function viewSource(name, table, root, description) {
107
+ const columns = table.columns.filter((c) => !c.client);
108
+ const width = Math.max(...columns.map((c) => c.name.length)) + 1;
109
+ const elements = columns.map((c) => {
110
+ const admin = ADMIN[c.type.dataElement ?? ""];
111
+ const lines = [` @EndUserText.label: '${label(elementName(c.name))}'`, ...(admin === undefined ? [] : [` ${admin}`])];
112
+ return `${lines.join("\n")}\n${c.key ? " key " : " "}${c.name.padEnd(width)}as ${elementName(c.name)}`;
113
+ });
114
+ return `@AccessControl.authorizationCheck: #NOT_REQUIRED
115
+ @EndUserText.label: '${description}'
116
+ define ${root ? "root " : ""}view entity ${name}
117
+ as select from ${table.name}
118
+ {
119
+ ${elements.join(",\n")}
120
+ }
121
+ `;
122
+ }
123
+ /**
124
+ * The name of the projection of another entity, by the same renaming as `name` ← `base`:
125
+ * ZC_Travel ← ZR_Travel gives ZC_Booking ← ZR_Booking.
126
+ */
127
+ function renamed(name, base, target) {
128
+ let common = 0;
129
+ while (common < Math.min(name.length, base.length) && name[name.length - 1 - common]?.toUpperCase() === base[base.length - 1 - common]?.toUpperCase()) {
130
+ common++;
131
+ }
132
+ const from = base.slice(0, base.length - common);
133
+ const to = name.slice(0, name.length - common);
134
+ return from !== "" && target.toUpperCase().startsWith(from.toUpperCase()) ? `${to}${target.slice(from.length)}` : target;
135
+ }
136
+ function projectionSource(name, base, description) {
137
+ const associations = base.associations.map((a) => {
138
+ const target = renamed(name, base.name, a.target);
139
+ switch (a.kind) {
140
+ case "composition":
141
+ return ` ${a.name} : redirected to composition child ${target}`;
142
+ case "toParent":
143
+ return ` ${a.name} : redirected to parent ${target}`;
144
+ default:
145
+ return ` ${a.name}`;
146
+ }
147
+ });
148
+ const elements = [...base.fields.map((f) => `${f.key ? " key " : " "}${f.name}`), ...associations];
149
+ return `@AccessControl.authorizationCheck: #NOT_REQUIRED
150
+ @EndUserText.label: '${description}'
151
+ @Metadata.allowExtensions: true
152
+ @Search.searchable: true
153
+ define ${base.root ? "root " : ""}view entity ${name}
154
+ provider contract transactional_query
155
+ as projection on ${base.name}
156
+ {
157
+ ${elements.join(",\n")}
158
+ }
159
+ `;
160
+ }
161
+ function abstractSource(name, description) {
162
+ return `@EndUserText.label: '${description}'
163
+ define abstract entity ${name}
164
+ {
165
+ Value : abap.char(40);
166
+ }
167
+ `;
168
+ }
169
+ function tableEntitySource(name, root, description) {
170
+ return `@ClientHandling.type: #CLIENT_DEPENDENT
171
+ @AbapCatalog.deliveryClass: #APPLICATION_DATA
172
+ @AccessControl.authorizationCheck: #NOT_REQUIRED
173
+ @EndUserText.label: '${description}'
174
+ define ${root ? "root " : ""}table entity ${name}
175
+ {
176
+ key ID : abap.char(10);
177
+ Description : abap.char(40);
178
+ }
179
+ `;
180
+ }
181
+ function simpleTypeSource(name, description) {
182
+ return `@EndUserText.label: '${description}'
183
+ define type ${name} : abap.char(40);
184
+ `;
185
+ }
186
+ function enumTypeSource(name, description) {
187
+ return `@EndUserText.label: '${description}'
188
+ define type ${name} : abap.char(1) enum
189
+ {
190
+ @EndUserText.label: 'Initial'
191
+ INITIAL_VALUE = initial;
192
+ @EndUserText.label: 'Value A'
193
+ VALUE_A = 'A';
194
+ }
195
+ `;
196
+ }
197
+ function customSource(name, query, description) {
198
+ return `@EndUserText.label: '${description}'
199
+ @ObjectModel.query.implementedBy: 'ABAP:${query.toUpperCase()}'
200
+ define custom entity ${name}
201
+ {
202
+ key ID : abap.char(10);
203
+ Description : abap.char(40);
204
+ }
205
+ `;
206
+ }
207
+ function queryClass(name, entityName) {
208
+ return `"! <p class="shorttext synchronized">Query of ${entityName}</p>
209
+ CLASS ${name.toLowerCase()} DEFINITION PUBLIC FINAL CREATE PUBLIC.
210
+ PUBLIC SECTION.
211
+ INTERFACES if_rap_query_provider.
212
+ ENDCLASS.
213
+
214
+
215
+
216
+ CLASS ${name.toLowerCase()} IMPLEMENTATION.
217
+
218
+ METHOD if_rap_query_provider~select.
219
+ DATA rows TYPE STANDARD TABLE OF ${entityName.toLowerCase()} WITH EMPTY KEY.
220
+ " read the data (an API, a table…), then apply the filter, the sort order and the paging
221
+ IF io_request->is_total_numb_of_rec_requested( ).
222
+ io_response->set_total_number_of_records( lines( rows ) ).
223
+ ENDIF.
224
+ IF io_request->is_data_requested( ).
225
+ io_response->set_data( rows ).
226
+ ENDIF.
227
+ ENDMETHOD.
228
+
229
+ ENDCLASS.
230
+ `;
231
+ }
232
+ function metadataSource(target) {
233
+ const visible = target.fields.filter((f) => !(isUuid(f.type) && f.key) && getAnnotation(f.annotations, "Semantics.systemDateTime.localInstanceLastChangedAt") !== true);
234
+ const title = visible.find((f) => !f.key || target.fields.length === 1) ?? target.fields[0];
235
+ const typeName = aliasOf(target.name);
236
+ const blocks = target.fields.map((f, i) => {
237
+ if (!visible.includes(f)) {
238
+ return `${i === 0 ? "" : "\n"} @UI.hidden: true\n ${f.name};`;
239
+ }
240
+ const position = (visible.indexOf(f) + 1) * 10;
241
+ const selection = visible.indexOf(f) < 3 ? `,\n selectionField: [ { position: ${position} } ]` : "";
242
+ return `${i === 0 ? "" : "\n"} @UI: { lineItem: [ { position: ${position}, importance: #${visible.indexOf(f) < 3 ? "HIGH" : "MEDIUM"} } ],
243
+ identification: [ { position: ${position} } ]${selection} }
244
+ ${f.name};`;
245
+ });
246
+ const facet = ` @UI.facet: [ { id: '${typeName}', purpose: #STANDARD, type: #IDENTIFICATION_REFERENCE, label: '${typeName}', position: 10 } ]\n`;
247
+ return `@Metadata.layer: #CORE
248
+ @UI.headerInfo: {
249
+ typeName: '${typeName}',
250
+ typeNamePlural: '${typeName.endsWith("s") ? typeName : `${typeName}s`}'${title === undefined ? "" : `,\n title: { type: #STANDARD, value: '${title.name}' }`}
251
+ }
252
+ annotate entity ${target.name} with
253
+ {
254
+ ${facet}${blocks.join("\n")}
255
+ }
256
+ `;
257
+ }
258
+ function accessSource(target, description) {
259
+ return `@EndUserText.label: '${description}'
260
+ @MappingRole: true
261
+ define role ${target.name} {
262
+ grant select on ${target.name};
263
+ }
264
+ `;
265
+ }
266
+ /** A managed BDEF on a root view entity and its compositions, or a projection BDEF. */
267
+ function behaviorSource(ctx, rootEntity) {
268
+ const tree = [];
269
+ const walk = (current, parent) => {
270
+ tree.push(parent === undefined ? { entity: current } : { entity: current, parent });
271
+ for (const composition of current.associations.filter((a) => a.kind === "composition")) {
272
+ const child = ctx.entities.resolve(composition.target);
273
+ const toParent = child?.associations.find((a) => a.kind === "toParent");
274
+ if (child !== undefined && toParent !== undefined) {
275
+ walk(child, { entity: current, toParent: toParent.name, foreignKey: toParent.on.map((o) => o.source) });
276
+ }
277
+ }
278
+ };
279
+ walk(rootEntity);
280
+ const compositions = (e) => e.associations.filter((a) => a.kind === "composition").map((a) => ` association ${a.name} { create; }`);
281
+ if (rootEntity.kind === "projection") {
282
+ return `projection;
283
+ strict ( 2 );
284
+ ${tree
285
+ .map(({ entity: e, parent }) => {
286
+ const etag = e.fields.some((f) => isAdmin(f, "localInstanceLastChangedAt")) ? "\nuse etag" : "";
287
+ const ops = parent === undefined ? [" use create;", " use update;", " use delete;"] : [" use update;", " use delete;"];
288
+ const associations = [...e.associations.filter((a) => a.kind === "composition").map((a) => ` use association ${a.name} { create; }`), ...(parent === undefined ? [] : [` use association ${parent.toParent};`])];
289
+ return `
290
+ define behavior for ${e.name} alias ${aliasOf(e.name)}${etag}
291
+ {
292
+ ${[...ops, ...(associations.length === 0 ? [] : ["", ...associations])].join("\n")}
293
+ }`;
294
+ })
295
+ .join("\n")}
296
+ `;
297
+ }
298
+ const root = tree[0]?.entity ?? rootEntity;
299
+ return `managed implementation in class ${poolName(root.name).toLowerCase()} unique;
300
+ strict ( 2 );
301
+ ${tree
302
+ .map(({ entity: e, parent }) => {
303
+ const table = e.source ?? "";
304
+ const etagField = e.fields.find((f) => isAdmin(f, "localInstanceLastChangedAt"));
305
+ const uuidKeys = e.fields.filter((f) => f.key && isUuid(f.type));
306
+ const readonly = e.fields.filter((f) => f.semantics !== undefined).map((f) => f.name);
307
+ const parentKeys = parent === undefined ? [] : e.fields.filter((f) => !f.key && parent.foreignKey.some((k) => k.toUpperCase() === f.name.toUpperCase())).map((f) => f.name);
308
+ const header = [
309
+ `define behavior for ${e.name} alias ${aliasOf(e.name)}`,
310
+ `persistent table ${table}`,
311
+ parent === undefined ? "lock master" : `lock dependent by ${parent.toParent}`,
312
+ parent === undefined ? "authorization master ( global )" : `authorization dependent by ${parent.toParent}`,
313
+ ...(etagField === undefined ? [] : [`etag master ${etagField.name}`]),
314
+ ];
315
+ const body = [
316
+ ...(parent === undefined ? [" create;", " update;", " delete;"] : [" update;", " delete;"]),
317
+ "",
318
+ ...(uuidKeys.length === 1 ? [` field ( numbering : managed, readonly ) ${uuidKeys[0]?.name ?? ""};`] : []),
319
+ ...(readonly.length + parentKeys.length > 0 ? [` field ( readonly ) ${[...parentKeys, ...readonly].join(", ")};`] : []),
320
+ ...compositions(e),
321
+ ...(parent === undefined ? [] : [` association ${parent.toParent};`]),
322
+ "",
323
+ ` mapping for ${table} corresponding`,
324
+ " {",
325
+ ...e.fields.filter((f) => f.column !== undefined).map((f) => ` ${f.name} = ${f.column ?? ""};`),
326
+ " }",
327
+ ];
328
+ return `\n${header.join("\n")}\n{\n${body.join("\n").replace(/\n\n\n/g, "\n\n")}\n}`;
329
+ })
330
+ .join("\n")}
331
+ `;
332
+ }
333
+ function isAdmin(field, semantics) {
334
+ return field.semantics === semantics;
335
+ }
336
+ function classSource(name, description) {
337
+ return `"! <p class="shorttext synchronized">${description}</p>
338
+ CLASS ${name.toLowerCase()} DEFINITION PUBLIC FINAL CREATE PUBLIC.
339
+ PUBLIC SECTION.
340
+ PROTECTED SECTION.
341
+ PRIVATE SECTION.
342
+ ENDCLASS.
343
+
344
+
345
+
346
+ CLASS ${name.toLowerCase()} IMPLEMENTATION.
347
+ ENDCLASS.
348
+ `;
349
+ }
350
+ function interfaceSource(name, description) {
351
+ return `"! <p class="shorttext synchronized">${description}</p>
352
+ INTERFACE ${name.toLowerCase()} PUBLIC.
353
+ ENDINTERFACE.
354
+ `;
355
+ }
356
+ /** The behavior pool of a managed BDEF: one handler per entity, global authorization. */
357
+ function poolSources(name, bdefName, bdefSource) {
358
+ const parsed = parseBdef(bdefSource);
359
+ if (parsed.ast === undefined) {
360
+ return `${bdefName}: the behavior definition does not parse (${parsed.errors[0]?.message ?? ""})`;
361
+ }
362
+ const root = parsed.ast.behaviors.find((b) => b.root) ?? parsed.ast.behaviors[0];
363
+ if (root === undefined) {
364
+ return `${bdefName}: no "define behavior for" in the behavior definition`;
365
+ }
366
+ const handlers = parsed.ast.behaviors.map((b) => {
367
+ const alias = b.alias?.text ?? b.entity.text;
368
+ const local = `lhc_${alias}`.slice(0, 30);
369
+ const authorization = b.options.some((o) => o.kind === "authorizationMaster");
370
+ const definition = authorization
371
+ ? `
372
+ PRIVATE SECTION.
373
+ METHODS get_global_authorizations FOR GLOBAL AUTHORIZATION
374
+ IMPORTING REQUEST requested_authorizations FOR ${alias} RESULT result.`
375
+ : "";
376
+ const implementation = authorization
377
+ ? `
378
+ METHOD get_global_authorizations.
379
+ " every activity is allowed: check the authorizations here (AUTHORITY-CHECK OBJECT …)
380
+ IF requested_authorizations-%create = if_abap_behv=>mk-on.
381
+ result-%create = if_abap_behv=>auth-allowed.
382
+ ENDIF.
383
+ IF requested_authorizations-%update = if_abap_behv=>mk-on.
384
+ result-%update = if_abap_behv=>auth-allowed.
385
+ ENDIF.
386
+ IF requested_authorizations-%delete = if_abap_behv=>mk-on.
387
+ result-%delete = if_abap_behv=>auth-allowed.
388
+ ENDIF.
389
+ ENDMETHOD.
390
+ `
391
+ : "";
392
+ return `CLASS ${local} DEFINITION INHERITING FROM cl_abap_behavior_handler.${definition}
393
+ ENDCLASS.
394
+
395
+ CLASS ${local} IMPLEMENTATION.${implementation}
396
+ ENDCLASS.`;
397
+ });
398
+ return {
399
+ global: `"! <p class="shorttext synchronized">Behavior pool of ${root.entity.text.toUpperCase()}</p>
400
+ CLASS ${name.toLowerCase()} DEFINITION PUBLIC ABSTRACT FINAL FOR BEHAVIOR OF ${root.entity.text.toLowerCase()}.
401
+ ENDCLASS.
402
+
403
+
404
+
405
+ CLASS ${name.toLowerCase()} IMPLEMENTATION.
406
+ ENDCLASS.
407
+ `,
408
+ locals: `${handlers.join("\n\n")}\n`,
409
+ };
410
+ }
411
+ function serviceSource(name, exposed, description) {
412
+ return `@EndUserText.label: '${description}'
413
+ define service ${name} {
414
+ ${exposed.map((e) => ` expose ${e} as ${aliasOf(e)};`).join("\n")}
415
+ }
416
+ `;
417
+ }
418
+ /** The DDIC type of a built-in type: DATATYPE, LENG, DECIMALS. */
419
+ function ddicOf(type) {
420
+ const datatype = type.kind === "bool" ? "CHAR" : type.kind === "timestamp" || type.kind === "timestampl" ? "DEC" : type.kind.toUpperCase();
421
+ const length = type.kind === "bool" ? 1 : type.kind === "timestamp" ? 15 : type.kind === "timestampl" ? 21 : type.length;
422
+ const decimals = type.kind === "timestampl" ? 7 : type.decimals;
423
+ return [
424
+ ["DATATYPE", datatype],
425
+ ["LENG", length === undefined ? undefined : String(length).padStart(6, "0")],
426
+ ["DECIMALS", decimals === undefined ? undefined : String(decimals).padStart(6, "0")],
427
+ ];
428
+ }
429
+ /** A data element (DTEL), on a domain or a built-in type, with its field labels. */
430
+ function dataElementXml(name, description, base) {
431
+ const text = description.slice(0, 60);
432
+ return abapGitXml("DTEL", xmlElement("DD04V", [
433
+ ["ROLLNAME", name.toUpperCase()],
434
+ ["DDLANGUAGE", "E"],
435
+ ...(base.domain === undefined ? [] : [["DOMNAME", base.domain.toUpperCase()]]),
436
+ ["HEADLEN", "55"],
437
+ ["SCRLEN1", "10"],
438
+ ["SCRLEN2", "20"],
439
+ ["SCRLEN3", "40"],
440
+ ["DDTEXT", text],
441
+ ["REPTEXT", text],
442
+ ["SCRTEXT_S", text.slice(0, 10)],
443
+ ["SCRTEXT_M", text.slice(0, 20)],
444
+ ["SCRTEXT_L", text.slice(0, 40)],
445
+ ["DTELMASTER", "E"],
446
+ ...(base.type === undefined ? [] : [["REFKIND", "D"], ...ddicOf(base.type)]),
447
+ ], 3));
448
+ }
449
+ /** A domain (DOMA) of a built-in type. */
450
+ function domainXml(name, description, type) {
451
+ return abapGitXml("DOMA", xmlElement("DD01V", [["DOMNAME", name.toUpperCase()], ["DDLANGUAGE", "E"], ...ddicOf(type), ["OUTPUTLEN", type.length === undefined ? undefined : String(type.length).padStart(6, "0")], ["DDTEXT", description.slice(0, 60)], ["DOMMASTER", "E"]], 3));
452
+ }
453
+ function bindingSource(name, service, description, webApi = false) {
454
+ const services = ` <SERVICES>
455
+ <item>
456
+ <NAME>${name.toUpperCase()}</NAME>
457
+ <VERSIONS>
458
+ <item>
459
+ <SERVICE_VERSION>0001</SERVICE_VERSION>
460
+ <SERVICE_DEFINITION>${service.toUpperCase()}</SERVICE_DEFINITION>
461
+ <RELEASE_STATE>NOT_RELEASED</RELEASE_STATE>
462
+ </item>
463
+ </VERSIONS>
464
+ </item>
465
+ </SERVICES>`;
466
+ const header = xmlElement("SRVB", [
467
+ ["NAME", name.toUpperCase()],
468
+ ["DESCRIPTION", description.slice(0, 60)],
469
+ ["LANGUAGE", "EN"],
470
+ ["BINDING_TYPE", "ODATA"],
471
+ ["BINDING_TYPE_VERSION", "V4"],
472
+ ["BINDING_TYPE_CATEGORY", webApi ? "1" : "0"],
473
+ ], 3);
474
+ return abapGitXml("SRVB", header.replace(/\n {3}<\/SRVB>$/, `\n${services}\n </SRVB>`));
475
+ }
476
+ // ------------------------------------------------------------------------------------- command
477
+ /** Writes a new object into the project `dir`. */
478
+ export function generateObject(dir, kind, name, options = {}) {
479
+ const root = path.resolve(dir);
480
+ if (!(kind in OBJECT_KINDS)) {
481
+ return { written: [], error: `${kind}: unknown kind, one of ${Object.keys(OBJECT_KINDS).join(", ")}` };
482
+ }
483
+ if (!NAME.test(name)) {
484
+ return { written: [], error: `${name}: an object name is made of letters, digits and _, starting with a letter` };
485
+ }
486
+ const limit = kind === "table" ? 16 : kind === "tableentity" ? 25 : 30;
487
+ if (name.length > limit) {
488
+ return { written: [], error: `${name}: at most ${limit} characters` };
489
+ }
490
+ const src = sourceFolder(root);
491
+ const description = options.description ?? label(aliasOf(name));
492
+ const lower = name.toLowerCase();
493
+ const files = [];
494
+ const ctx = () => context(root);
495
+ switch (kind) {
496
+ case "table":
497
+ files.push([`${lower}.tabl.astabl`, tableSource(lower, description)]);
498
+ break;
499
+ case "view": {
500
+ if (options.from === undefined) {
501
+ return { written: [], error: "rapt generate view needs the table: --from <table>" };
502
+ }
503
+ const table = ctx().dictionary.table(options.from.toLowerCase(), new Diagnostics());
504
+ if (table === undefined) {
505
+ return { written: [], error: `${options.from}: no table of that name in the sources` };
506
+ }
507
+ files.push([`${lower}.ddls.asddls`, viewSource(name, table, options.root === true, description)]);
508
+ break;
509
+ }
510
+ case "projection": {
511
+ const base = entity(ctx(), options.from, "rapt generate projection");
512
+ if (typeof base === "string")
513
+ return { written: [], error: base };
514
+ files.push([`${lower}.ddls.asddls`, projectionSource(name, base, description)]);
515
+ break;
516
+ }
517
+ case "abstract":
518
+ files.push([`${lower}.ddls.asddls`, abstractSource(name, description)]);
519
+ break;
520
+ case "tableentity":
521
+ files.push([`${lower}.ddls.asddls`, tableEntitySource(name, options.root === true, description)]);
522
+ break;
523
+ case "type":
524
+ files.push([`${lower}.drty.acds`, simpleTypeSource(name, description)]);
525
+ break;
526
+ case "enum":
527
+ files.push([`${lower}.drty.acds`, enumTypeSource(name, description)]);
528
+ break;
529
+ case "custom": {
530
+ const query = options.for ?? `zcl_${lower.replace(/^[zy]_?/, "")}_query`.slice(0, 30);
531
+ files.push([`${lower}.ddls.asddls`, customSource(name, query, description)], [`${query.toLowerCase()}.clas.abap`, queryClass(query, name)]);
532
+ break;
533
+ }
534
+ case "metadata": {
535
+ const target = entity(ctx(), options.from ?? name, "rapt generate metadata");
536
+ if (typeof target === "string")
537
+ return { written: [], error: target };
538
+ files.push([`${lower}.ddlx.asddlxs`, metadataSource(target)]);
539
+ break;
540
+ }
541
+ case "access": {
542
+ const target = entity(ctx(), options.from ?? name, "rapt generate access");
543
+ if (typeof target === "string")
544
+ return { written: [], error: target };
545
+ files.push([`${lower}.dcls.asdcls`, accessSource(target, description)]);
546
+ break;
547
+ }
548
+ case "behavior": {
549
+ const c = ctx();
550
+ const target = entity(c, options.from ?? name, "rapt generate behavior");
551
+ if (typeof target === "string")
552
+ return { written: [], error: target };
553
+ if (!target.root) {
554
+ return { written: [], error: `${target.name}: a behavior definition is defined for a root entity` };
555
+ }
556
+ if (target.kind !== "view" && target.kind !== "projection") {
557
+ return { written: [], error: `${target.name}: a behavior definition needs a view entity or a projection` };
558
+ }
559
+ files.push([`${lower}.bdef.asbdef`, behaviorSource(c, target)]);
560
+ break;
561
+ }
562
+ case "pool": {
563
+ const project = loadProject(root);
564
+ const bdefs = filesOfType(project, "BDEF").filter((f) => f.extension === "asbdef");
565
+ const bdef = options.for === undefined
566
+ ? bdefs.find((f) => new RegExp(`implementation\\s+in\\s+class\\s+${name}\\b`, "i").test(f.content))
567
+ : bdefs.find((f) => f.name === options.for?.toUpperCase());
568
+ if (bdef === undefined) {
569
+ return { written: [], error: options.for === undefined ? `no behavior definition names the class ${name}: give it with --for <BDEF>` : `${options.for}: no behavior definition of that name` };
570
+ }
571
+ const sources = poolSources(name, bdef.name, bdef.content);
572
+ if (typeof sources === "string")
573
+ return { written: [], error: sources };
574
+ files.push([`${lower}.clas.abap`, sources.global], [`${lower}.clas.locals_imp.abap`, sources.locals]);
575
+ break;
576
+ }
577
+ case "class":
578
+ files.push([`${lower}.clas.abap`, classSource(name, description)]);
579
+ break;
580
+ case "interface":
581
+ files.push([`${lower}.intf.abap`, interfaceSource(name, description)]);
582
+ break;
583
+ case "service": {
584
+ const exposed = options.expose ?? (options.from === undefined ? [] : [options.from]);
585
+ if (exposed.length === 0) {
586
+ return { written: [], error: "rapt generate service needs the exposed entities: --expose A,B" };
587
+ }
588
+ files.push([`${lower}.srvd.srvdsrv`, serviceSource(name, exposed, description)]);
589
+ break;
590
+ }
591
+ case "dataelement":
592
+ case "domain": {
593
+ const builtIn = parseBuiltInType(options.type ?? (kind === "domain" || options.from === undefined ? "abap.char(10)" : ""));
594
+ if (kind === "dataelement" && options.from !== undefined) {
595
+ files.push([`${lower}.dtel.xml`, dataElementXml(name, description, { domain: options.from })]);
596
+ break;
597
+ }
598
+ if (builtIn === undefined) {
599
+ return { written: [], error: `${options.type ?? ""}: a built-in type is expected, e.g. --type abap.char(10)` };
600
+ }
601
+ files.push(kind === "domain" ? [`${lower}.doma.xml`, domainXml(name, description, builtIn)] : [`${lower}.dtel.xml`, dataElementXml(name, description, { type: builtIn })]);
602
+ break;
603
+ }
604
+ case "binding": {
605
+ const service = options.for ?? options.from;
606
+ if (service === undefined) {
607
+ return { written: [], error: "rapt generate binding needs the service definition: --for <SRVD>" };
608
+ }
609
+ // --binding-type webapi: OData V4 - Web API (D60)
610
+ const webApi = options.bindingType?.toLowerCase() === "webapi";
611
+ files.push([`${lower}.srvb.xml`, bindingSource(name, service, `${description} (OData V4, ${webApi ? "Web API" : "UI"})`, webApi)]);
612
+ break;
613
+ }
614
+ }
615
+ const absolute = files.map(([file, content]) => [path.join(src, file), content]);
616
+ const existing = absolute.filter(([file]) => existsSync(file)).map(([file]) => path.relative(root, file));
617
+ if (existing.length > 0) {
618
+ return { written: [], error: `already present: ${existing.join(", ")}` };
619
+ }
620
+ const written = [];
621
+ for (const [file, content] of absolute) {
622
+ mkdirSync(path.dirname(file), { recursive: true });
623
+ writeFileSync(file, content);
624
+ written.push(path.relative(root, file).split(path.sep).join("/"));
625
+ }
626
+ const abapGit = generateAbapGitFiles(root);
627
+ written.push(...abapGit.written.filter((f) => !written.includes(f)));
628
+ return { written };
629
+ }
630
+ //# sourceMappingURL=generate.js.map