@open-rap/compiler 0.0.0-stage → 0.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +21 -2
- package/dist/agent/server.d.ts +35 -0
- package/dist/agent/server.js +408 -0
- package/dist/agent/server.js.map +1 -0
- package/dist/authoring/abapgit.d.ts +38 -0
- package/dist/authoring/abapgit.js +242 -0
- package/dist/authoring/abapgit.js.map +1 -0
- package/dist/authoring/catalog.d.ts +48 -0
- package/dist/authoring/catalog.js +117 -0
- package/dist/authoring/catalog.js.map +1 -0
- package/dist/authoring/floorplan.d.ts +28 -0
- package/dist/authoring/floorplan.js +31 -0
- package/dist/authoring/floorplan.js.map +1 -0
- package/dist/authoring/generate.d.ts +57 -0
- package/dist/authoring/generate.js +630 -0
- package/dist/authoring/generate.js.map +1 -0
- package/dist/authoring/mcp.d.ts +15 -0
- package/dist/authoring/mcp.js +52 -0
- package/dist/authoring/mcp.js.map +1 -0
- package/dist/authoring/new.d.ts +35 -0
- package/dist/authoring/new.js +389 -0
- package/dist/authoring/new.js.map +1 -0
- package/dist/bdef/ast.d.ts +249 -0
- package/dist/bdef/ast.js +3 -0
- package/dist/bdef/ast.js.map +1 -0
- package/dist/bdef/lexer.d.ts +105 -0
- package/dist/bdef/lexer.js +148 -0
- package/dist/bdef/lexer.js.map +1 -0
- package/dist/bdef/parser.d.ts +22 -0
- package/dist/bdef/parser.js +1012 -0
- package/dist/bdef/parser.js.map +1 -0
- package/dist/build.d.ts +26 -0
- package/dist/build.js +62 -0
- package/dist/build.js.map +1 -0
- package/dist/cds/adapter.d.ts +91 -0
- package/dist/cds/adapter.js +381 -0
- package/dist/cds/adapter.js.map +1 -0
- package/dist/cds/expression-ast.d.ts +65 -0
- package/dist/cds/expression-ast.js +330 -0
- package/dist/cds/expression-ast.js.map +1 -0
- package/dist/cli.d.ts +9 -0
- package/dist/cli.js +485 -0
- package/dist/cli.js.map +1 -0
- package/dist/dcl/parser.d.ts +59 -0
- package/dist/dcl/parser.js +338 -0
- package/dist/dcl/parser.js.map +1 -0
- package/dist/ddic/catalog.d.ts +17 -0
- package/dist/ddic/catalog.js +211 -0
- package/dist/ddic/catalog.js.map +1 -0
- package/dist/ddic/cds-types.d.ts +47 -0
- package/dist/ddic/cds-types.js +131 -0
- package/dist/ddic/cds-types.js.map +1 -0
- package/dist/ddic/table-source.d.ts +70 -0
- package/dist/ddic/table-source.js +500 -0
- package/dist/ddic/table-source.js.map +1 -0
- package/dist/ddic/xml.d.ts +65 -0
- package/dist/ddic/xml.js +201 -0
- package/dist/ddic/xml.js.map +1 -0
- package/dist/dev.d.ts +128 -0
- package/dist/dev.js +434 -0
- package/dist/dev.js.map +1 -0
- package/dist/diagnostics.d.ts +33 -0
- package/dist/diagnostics.js +53 -0
- package/dist/diagnostics.js.map +1 -0
- package/dist/gen/abap/derived-types.d.ts +26 -0
- package/dist/gen/abap/derived-types.js +323 -0
- package/dist/gen/abap/derived-types.js.map +1 -0
- package/dist/gen/abap/eml.d.ts +118 -0
- package/dist/gen/abap/eml.js +362 -0
- package/dist/gen/abap/eml.js.map +1 -0
- package/dist/gen/abap/enums.d.ts +13 -0
- package/dist/gen/abap/enums.js +74 -0
- package/dist/gen/abap/enums.js.map +1 -0
- package/dist/gen/abap/framework-http.d.ts +14 -0
- package/dist/gen/abap/framework-http.js +1049 -0
- package/dist/gen/abap/framework-http.js.map +1 -0
- package/dist/gen/abap/framework-query.d.ts +14 -0
- package/dist/gen/abap/framework-query.js +225 -0
- package/dist/gen/abap/framework-query.js.map +1 -0
- package/dist/gen/abap/framework-utils.d.ts +29 -0
- package/dist/gen/abap/framework-utils.js +3707 -0
- package/dist/gen/abap/framework-utils.js.map +1 -0
- package/dist/gen/abap/framework.d.ts +12 -0
- package/dist/gen/abap/framework.js +480 -0
- package/dist/gen/abap/framework.js.map +1 -0
- package/dist/gen/abap/generate.d.ts +40 -0
- package/dist/gen/abap/generate.js +238 -0
- package/dist/gen/abap/generate.js.map +1 -0
- package/dist/gen/abap/rewrite.d.ts +82 -0
- package/dist/gen/abap/rewrite.js +957 -0
- package/dist/gen/abap/rewrite.js.map +1 -0
- package/dist/gen/abap/shadow.d.ts +25 -0
- package/dist/gen/abap/shadow.js +175 -0
- package/dist/gen/abap/shadow.js.map +1 -0
- package/dist/gen/abap/tokens.d.ts +33 -0
- package/dist/gen/abap/tokens.js +87 -0
- package/dist/gen/abap/tokens.js.map +1 -0
- package/dist/gen/app/index.d.ts +25 -0
- package/dist/gen/app/index.js +322 -0
- package/dist/gen/app/index.js.map +1 -0
- package/dist/gen/csdl.d.ts +11 -0
- package/dist/gen/csdl.js +562 -0
- package/dist/gen/csdl.js.map +1 -0
- package/dist/gen/deploy.d.ts +12 -0
- package/dist/gen/deploy.js +53 -0
- package/dist/gen/deploy.js.map +1 -0
- package/dist/gen/docs/index.d.ts +35 -0
- package/dist/gen/docs/index.js +267 -0
- package/dist/gen/docs/index.js.map +1 -0
- package/dist/gen/docs/markdown.d.ts +23 -0
- package/dist/gen/docs/markdown.js +187 -0
- package/dist/gen/docs/markdown.js.map +1 -0
- package/dist/gen/docs/site.d.ts +17 -0
- package/dist/gen/docs/site.js +106 -0
- package/dist/gen/docs/site.js.map +1 -0
- package/dist/gen/keycloak.d.ts +21 -0
- package/dist/gen/keycloak.js +90 -0
- package/dist/gen/keycloak.js.map +1 -0
- package/dist/gen/launchpad/index.d.ts +48 -0
- package/dist/gen/launchpad/index.js +302 -0
- package/dist/gen/launchpad/index.js.map +1 -0
- package/dist/gen/launchpad/ui.d.ts +14 -0
- package/dist/gen/launchpad/ui.js +350 -0
- package/dist/gen/launchpad/ui.js.map +1 -0
- package/dist/gen/mcp.d.ts +23 -0
- package/dist/gen/mcp.js +244 -0
- package/dist/gen/mcp.js.map +1 -0
- package/dist/gen/sql-expressions.d.ts +12 -0
- package/dist/gen/sql-expressions.js +136 -0
- package/dist/gen/sql-expressions.js.map +1 -0
- package/dist/gen/sql.d.ts +38 -0
- package/dist/gen/sql.js +612 -0
- package/dist/gen/sql.js.map +1 -0
- package/dist/gen/ui/index.d.ts +7 -0
- package/dist/gen/ui/index.js +27 -0
- package/dist/gen/ui/index.js.map +1 -0
- package/dist/gen/ui/layout.d.ts +230 -0
- package/dist/gen/ui/layout.js +511 -0
- package/dist/gen/ui/layout.js.map +1 -0
- package/dist/gen/ui/static.d.ts +55 -0
- package/dist/gen/ui/static.js +2901 -0
- package/dist/gen/ui/static.js.map +1 -0
- package/dist/gen/ui/views.d.ts +10 -0
- package/dist/gen/ui/views.js +511 -0
- package/dist/gen/ui/views.js.map +1 -0
- package/dist/gen/vendor.d.ts +23 -0
- package/dist/gen/vendor.js +68 -0
- package/dist/gen/vendor.js.map +1 -0
- package/dist/gen/xml.d.ts +9 -0
- package/dist/gen/xml.js +42 -0
- package/dist/gen/xml.js.map +1 -0
- package/dist/index.d.ts +20 -0
- package/dist/index.js +20 -0
- package/dist/index.js.map +1 -0
- package/dist/input/project.d.ts +50 -0
- package/dist/input/project.js +128 -0
- package/dist/input/project.js.map +1 -0
- package/dist/ir/access-controls.d.ts +11 -0
- package/dist/ir/access-controls.js +148 -0
- package/dist/ir/access-controls.js.map +1 -0
- package/dist/ir/authorizations.d.ts +25 -0
- package/dist/ir/authorizations.js +178 -0
- package/dist/ir/authorizations.js.map +1 -0
- package/dist/ir/behaviors.d.ts +16 -0
- package/dist/ir/behaviors.js +548 -0
- package/dist/ir/behaviors.js.map +1 -0
- package/dist/ir/builder.d.ts +20 -0
- package/dist/ir/builder.js +505 -0
- package/dist/ir/builder.js.map +1 -0
- package/dist/ir/config.d.ts +25 -0
- package/dist/ir/config.js +130 -0
- package/dist/ir/config.js.map +1 -0
- package/dist/ir/dictionary.d.ts +54 -0
- package/dist/ir/dictionary.js +328 -0
- package/dist/ir/dictionary.js.map +1 -0
- package/dist/ir/entities.d.ts +58 -0
- package/dist/ir/entities.js +676 -0
- package/dist/ir/entities.js.map +1 -0
- package/dist/ir/expressions.d.ts +33 -0
- package/dist/ir/expressions.js +327 -0
- package/dist/ir/expressions.js.map +1 -0
- package/dist/ir/handlers.d.ts +63 -0
- package/dist/ir/handlers.js +398 -0
- package/dist/ir/handlers.js.map +1 -0
- package/dist/ir/number-ranges.d.ts +24 -0
- package/dist/ir/number-ranges.js +74 -0
- package/dist/ir/number-ranges.js.map +1 -0
- package/dist/ir/seed.d.ts +23 -0
- package/dist/ir/seed.js +78 -0
- package/dist/ir/seed.js.map +1 -0
- package/dist/ir/service.d.ts +21 -0
- package/dist/ir/service.js +96 -0
- package/dist/ir/service.js.map +1 -0
- package/dist/launchpad/config.d.ts +47 -0
- package/dist/launchpad/config.js +321 -0
- package/dist/launchpad/config.js.map +1 -0
- package/dist/launchpad/workspace.d.ts +48 -0
- package/dist/launchpad/workspace.js +166 -0
- package/dist/launchpad/workspace.js.map +1 -0
- package/dist/srvd/parser.d.ts +36 -0
- package/dist/srvd/parser.js +193 -0
- package/dist/srvd/parser.js.map +1 -0
- package/dist/standard/data.d.ts +18 -0
- package/dist/standard/data.js +534 -0
- package/dist/standard/data.js.map +1 -0
- package/dist/standard/index.d.ts +36 -0
- package/dist/standard/index.js +507 -0
- package/dist/standard/index.js.map +1 -0
- package/dist/version.d.ts +2 -0
- package/dist/version.js +4 -0
- package/dist/version.js.map +1 -0
- package/docs/AGENTS.md +79 -0
- package/docs/ANNOTATIONS.md +76 -0
- package/docs/DECISIONS.md +1554 -0
- package/docs/DEPLOYMENT.md +113 -0
- package/docs/DIAGNOSTICS.md +116 -0
- package/docs/FEATURES.md +287 -0
- package/docs/GUIDE.md +560 -0
- package/docs/KNOWN_GAPS.md +216 -0
- package/docs/MCP.md +84 -0
- package/docs/PLAN.md +690 -0
- package/docs/README.md +148 -0
- package/docs/REMOTE_APIS.md +121 -0
- package/docs/ROADMAP.md +35 -0
- package/docs/UTILITIES.md +95 -0
- package/package.json +49 -3
package/dist/gen/csdl.js
ADDED
|
@@ -0,0 +1,562 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* $metadata (CSDL XML, OData V4) of the service, with the UI, Common, Core, Capabilities and
|
|
3
|
+
* Measures annotations derived from the CDS annotations and the behavior (PLAN.md §6.5).
|
|
4
|
+
*/
|
|
5
|
+
import { buildODataService, CONTAINER_NAME, ENTITY_CONTROL, exposedBehavior, FIELD_CONTROL, fieldControlTypeName, findEntity, getAnnotation, valueHelpOf, getArray, OPERATION_CONTROL, operationControlTypeName, projectedFieldName, same, SCHEMA_ALIAS, } from "@open-rap/model";
|
|
6
|
+
import { el, renderXml } from "./xml.js";
|
|
7
|
+
const VOCABULARIES = [
|
|
8
|
+
{ uri: "https://oasis-tcs.github.io/odata-vocabularies/vocabularies/Org.OData.Core.V1.xml", namespace: "Org.OData.Core.V1", alias: "Core" },
|
|
9
|
+
{ uri: "https://oasis-tcs.github.io/odata-vocabularies/vocabularies/Org.OData.Capabilities.V1.xml", namespace: "Org.OData.Capabilities.V1", alias: "Capabilities" },
|
|
10
|
+
{ uri: "https://oasis-tcs.github.io/odata-vocabularies/vocabularies/Org.OData.Measures.V1.xml", namespace: "Org.OData.Measures.V1", alias: "Measures" },
|
|
11
|
+
{ uri: "https://sap.github.io/odata-vocabularies/vocabularies/Common.xml", namespace: "com.sap.vocabularies.Common.v1", alias: "Common" },
|
|
12
|
+
{ uri: "https://sap.github.io/odata-vocabularies/vocabularies/UI.xml", namespace: "com.sap.vocabularies.UI.v1", alias: "UI" },
|
|
13
|
+
{ uri: "https://sap.github.io/odata-vocabularies/vocabularies/Communication.xml", namespace: "com.sap.vocabularies.Communication.v1", alias: "Communication" },
|
|
14
|
+
];
|
|
15
|
+
const isObject = (v) => typeof v === "object" && v !== null && !Array.isArray(v);
|
|
16
|
+
const str = (v) => (typeof v === "string" ? v : typeof v === "number" ? String(v) : undefined);
|
|
17
|
+
/**
|
|
18
|
+
* The AllowedExpressions of a filter field (FilterExpressionRestrictions) from
|
|
19
|
+
* `@Consumption.filter.selectionType` and `multipleSelections` (D57, D68); undefined: any condition.
|
|
20
|
+
*/
|
|
21
|
+
export function allowedFilterExpressions(annotations) {
|
|
22
|
+
const selection = str(getAnnotation(annotations, "Consumption.filter.selectionType"))?.replace(/^#/, "").toUpperCase();
|
|
23
|
+
const single = getAnnotation(annotations, "Consumption.filter.multipleSelections") === false;
|
|
24
|
+
if (selection === "SINGLE" || (single && selection === undefined))
|
|
25
|
+
return "SingleValue";
|
|
26
|
+
if (selection === "INTERVAL")
|
|
27
|
+
return single ? "SingleRange" : "MultiRange";
|
|
28
|
+
return selection === "RANGE" && single ? "SingleRange" : undefined;
|
|
29
|
+
}
|
|
30
|
+
const num = (v) => (typeof v === "number" ? v : Number.POSITIVE_INFINITY);
|
|
31
|
+
/** `#HIGH` → `High`, `#FOR_ACTION` → `ForAction`. */
|
|
32
|
+
function enumName(value) {
|
|
33
|
+
return (value ?? "")
|
|
34
|
+
.replace(/^#/, "")
|
|
35
|
+
.toLowerCase()
|
|
36
|
+
.split("_")
|
|
37
|
+
.map((p) => (p === "" ? "" : p[0]?.toUpperCase() + p.slice(1)))
|
|
38
|
+
.join("");
|
|
39
|
+
}
|
|
40
|
+
/** Terms a Web API binding leaves out (D60): UI information, value helps, side effects. */
|
|
41
|
+
const UI_ONLY = /^(UI\.|Common\.ValueList|Common\.SideEffects|Common\.FilterDefaultValue)/;
|
|
42
|
+
/** An annotation of a Web API: without the UI information, nested annotations included. */
|
|
43
|
+
function webApiAnnotation(element) {
|
|
44
|
+
const term = element.attributes?.["Term"];
|
|
45
|
+
if (element.name === "Annotation" && typeof term === "string" && UI_ONLY.test(term)) {
|
|
46
|
+
return undefined;
|
|
47
|
+
}
|
|
48
|
+
return {
|
|
49
|
+
...element,
|
|
50
|
+
children: (element.children ?? []).map((c) => (typeof c === "object" && c !== null ? webApiAnnotation(c) : c)),
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
function edmAttributes(edm) {
|
|
54
|
+
if (edm.enum !== undefined) {
|
|
55
|
+
// an OData enumeration type of the enhanced mode (D55)
|
|
56
|
+
return { Type: `${SCHEMA_ALIAS}.${edm.enum.name}` };
|
|
57
|
+
}
|
|
58
|
+
const attributes = { Type: edm.type };
|
|
59
|
+
if (edm.maxLength !== undefined)
|
|
60
|
+
attributes["MaxLength"] = edm.maxLength;
|
|
61
|
+
if (edm.precision !== undefined)
|
|
62
|
+
attributes["Precision"] = edm.precision;
|
|
63
|
+
if (edm.type === "Edm.Decimal")
|
|
64
|
+
attributes["Scale"] = edm.scale ?? "variable";
|
|
65
|
+
return attributes;
|
|
66
|
+
}
|
|
67
|
+
const annotation = (term, attributes = {}, ...children) => el("Annotation", { Term: term, ...attributes }, ...children);
|
|
68
|
+
const pv = (property, attributes, ...children) => el("PropertyValue", { Property: property, ...attributes }, ...children);
|
|
69
|
+
const record = (type, ...children) => el("Record", type === undefined ? undefined : { Type: type }, ...children);
|
|
70
|
+
const collection = (...children) => el("Collection", undefined, ...children);
|
|
71
|
+
const propertyPath = (path) => el("PropertyPath", undefined, path);
|
|
72
|
+
/** The entries of an element-level array annotation (`@UI.lineItem`) over all fields, by position. */
|
|
73
|
+
function entriesOf(entity, path) {
|
|
74
|
+
const result = [];
|
|
75
|
+
for (const field of entity.fields) {
|
|
76
|
+
for (const entry of getArray(field.annotations, path) ?? []) {
|
|
77
|
+
if (isObject(entry)) {
|
|
78
|
+
result.push({ field: field.name, entry });
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
return result.sort((a, b) => num(a.entry["position"]) - num(b.entry["position"]));
|
|
83
|
+
}
|
|
84
|
+
class CsdlBuilder {
|
|
85
|
+
model;
|
|
86
|
+
service;
|
|
87
|
+
qualified;
|
|
88
|
+
annotationBlocks = [];
|
|
89
|
+
constructor(model, service) {
|
|
90
|
+
this.model = model;
|
|
91
|
+
this.service = service;
|
|
92
|
+
this.qualified = (name) => `${SCHEMA_ALIAS}.${name}`;
|
|
93
|
+
}
|
|
94
|
+
build() {
|
|
95
|
+
const schemaChildren = [];
|
|
96
|
+
for (const type of this.service.entityTypes) {
|
|
97
|
+
schemaChildren.push(this.entityType(type));
|
|
98
|
+
}
|
|
99
|
+
schemaChildren.push(...this.complexTypes());
|
|
100
|
+
for (const operation of this.service.operations) {
|
|
101
|
+
schemaChildren.push(this.operation(operation));
|
|
102
|
+
}
|
|
103
|
+
schemaChildren.push(this.container());
|
|
104
|
+
for (const type of this.service.entityTypes) {
|
|
105
|
+
const entity = findEntity(this.model, type.entity);
|
|
106
|
+
if (entity !== undefined) {
|
|
107
|
+
this.propertyAnnotations(type, entity);
|
|
108
|
+
this.typeAnnotations(type, entity);
|
|
109
|
+
this.setAnnotations(type);
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
for (const operation of this.service.operations) {
|
|
113
|
+
this.operationAnnotations(operation);
|
|
114
|
+
}
|
|
115
|
+
schemaChildren.push(...this.annotationBlocks);
|
|
116
|
+
const edmx = el("edmx:Edmx", { Version: "4.0", "xmlns:edmx": "http://docs.oasis-open.org/odata/ns/edmx" }, ...VOCABULARIES.map((v) => el("edmx:Reference", { Uri: v.uri }, el("edmx:Include", { Namespace: v.namespace, Alias: v.alias }))), el("edmx:DataServices", undefined, el("Schema", { Namespace: this.service.namespace, Alias: SCHEMA_ALIAS, xmlns: "http://docs.oasis-open.org/odata/ns/edm" }, ...schemaChildren)));
|
|
117
|
+
return renderXml(edmx);
|
|
118
|
+
}
|
|
119
|
+
// -------------------------------------------------------------------------------------------
|
|
120
|
+
// types
|
|
121
|
+
entityType(type) {
|
|
122
|
+
const children = [el("Key", undefined, ...type.keys.map((k) => el("PropertyRef", { Name: k })))];
|
|
123
|
+
for (const property of type.properties) {
|
|
124
|
+
children.push(el("Property", { Name: property.name, ...edmAttributes(property.edm), Nullable: property.nullable ? undefined : "false" }));
|
|
125
|
+
}
|
|
126
|
+
if (type.entityControl !== undefined) {
|
|
127
|
+
children.push(el("Property", { Name: ENTITY_CONTROL, Type: this.qualified("EntityControl") }));
|
|
128
|
+
}
|
|
129
|
+
if (type.operationControl.length > 0) {
|
|
130
|
+
children.push(el("Property", { Name: OPERATION_CONTROL, Type: this.qualified(operationControlTypeName(type)) }));
|
|
131
|
+
}
|
|
132
|
+
if (type.fieldControl.length > 0) {
|
|
133
|
+
children.push(el("Property", { Name: FIELD_CONTROL, Type: this.qualified(fieldControlTypeName(type)) }));
|
|
134
|
+
}
|
|
135
|
+
for (const navigation of type.navigations) {
|
|
136
|
+
const targetType = this.qualified(navigation.targetType);
|
|
137
|
+
children.push(el("NavigationProperty", {
|
|
138
|
+
Name: navigation.name,
|
|
139
|
+
Type: navigation.collection ? `Collection(${targetType})` : targetType,
|
|
140
|
+
Nullable: navigation.collection || navigation.nullable ? undefined : "false",
|
|
141
|
+
Partner: navigation.partner,
|
|
142
|
+
}, ...navigation.constraints.map((c) => el("ReferentialConstraint", { Property: c.property, ReferencedProperty: c.referencedProperty }))));
|
|
143
|
+
}
|
|
144
|
+
return el("EntityType", { Name: type.name }, ...children);
|
|
145
|
+
}
|
|
146
|
+
complexTypes() {
|
|
147
|
+
const result = [];
|
|
148
|
+
if (this.service.entityTypes.some((t) => t.entityControl !== undefined)) {
|
|
149
|
+
result.push(el("ComplexType", { Name: "EntityControl" }, el("Property", { Name: "Deletable", Type: "Edm.Boolean", Nullable: "false" }), el("Property", { Name: "Updatable", Type: "Edm.Boolean", Nullable: "false" })));
|
|
150
|
+
}
|
|
151
|
+
for (const type of this.service.entityTypes) {
|
|
152
|
+
if (type.operationControl.length > 0) {
|
|
153
|
+
result.push(el("ComplexType", { Name: operationControlTypeName(type) }, ...type.operationControl.map((o) => el("Property", { Name: o, Type: "Edm.Boolean", Nullable: "false" }))));
|
|
154
|
+
}
|
|
155
|
+
if (type.fieldControl.length > 0) {
|
|
156
|
+
result.push(el("ComplexType", { Name: fieldControlTypeName(type) }, ...type.fieldControl.map((f) => el("Property", { Name: f, Type: "Edm.Byte", Nullable: "false" }))));
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
for (const complex of this.service.complexTypes) {
|
|
160
|
+
result.push(el("ComplexType", { Name: complex.name }, ...complex.properties.map((p) => el("Property", { Name: p.name, ...edmAttributes(p.edm), Nullable: p.nullable ? undefined : "false" }))));
|
|
161
|
+
}
|
|
162
|
+
for (const enumeration of this.service.enumTypes ?? []) {
|
|
163
|
+
result.push(el("EnumType", { Name: enumeration.name, UnderlyingType: enumeration.underlyingType === "Edm.Int32" ? undefined : enumeration.underlyingType }, ...enumeration.members.map((m) => el("Member", { Name: m.name, Value: m.value }, m.label === undefined ? undefined : annotation("Core.Description", { String: m.label })))));
|
|
164
|
+
}
|
|
165
|
+
return result;
|
|
166
|
+
}
|
|
167
|
+
bindingType(operation) {
|
|
168
|
+
const type = this.qualified(operation.boundType);
|
|
169
|
+
return operation.collectionBound ? `Collection(${type})` : type;
|
|
170
|
+
}
|
|
171
|
+
operation(operation) {
|
|
172
|
+
const returnsSelf = operation.returnType?.entityType === true && operation.returnType.type === operation.boundType;
|
|
173
|
+
const children = [el("Parameter", { Name: "_it", Type: this.bindingType(operation), Nullable: "false" })];
|
|
174
|
+
for (const parameter of operation.parameters) {
|
|
175
|
+
if (parameter.complexType !== undefined) {
|
|
176
|
+
const type = `${SCHEMA_ALIAS}.${parameter.complexType}`;
|
|
177
|
+
children.push(el("Parameter", { Name: parameter.name, Type: parameter.collection === true ? `Collection(${type})` : type, Nullable: parameter.nullable ? undefined : "false" }));
|
|
178
|
+
continue;
|
|
179
|
+
}
|
|
180
|
+
children.push(el("Parameter", { Name: parameter.name, ...edmAttributes(parameter.edm), Nullable: parameter.nullable ? undefined : "false" }));
|
|
181
|
+
}
|
|
182
|
+
if (operation.returnType !== undefined) {
|
|
183
|
+
const type = this.qualified(operation.returnType.type);
|
|
184
|
+
children.push(el("ReturnType", { Type: operation.returnType.collection ? `Collection(${type})` : type, Nullable: operation.returnType.nullable ? undefined : "false" }));
|
|
185
|
+
}
|
|
186
|
+
return el(operation.kind === "action" ? "Action" : "Function", { Name: operation.name, IsBound: "true", EntitySetPath: returnsSelf ? "_it" : undefined }, ...children);
|
|
187
|
+
}
|
|
188
|
+
container() {
|
|
189
|
+
return el("EntityContainer", { Name: CONTAINER_NAME }, ...this.service.entitySets.map((set) => {
|
|
190
|
+
const type = this.service.entityTypes.find((t) => t.name === set.entityType);
|
|
191
|
+
return el("EntitySet", { Name: set.name, EntityType: this.qualified(set.entityType) }, ...(type?.navigations ?? []).map((n) => el("NavigationPropertyBinding", { Path: n.name, Target: n.targetSet })));
|
|
192
|
+
}));
|
|
193
|
+
}
|
|
194
|
+
// -------------------------------------------------------------------------------------------
|
|
195
|
+
// annotations
|
|
196
|
+
annotations(target, children) {
|
|
197
|
+
const list = children.filter((c) => c !== undefined).map((c) => (this.model.service.webApi === true ? webApiAnnotation(c) : c)).filter((c) => c !== undefined);
|
|
198
|
+
if (list.length > 0) {
|
|
199
|
+
this.annotationBlocks.push(el("Annotations", { Target: target }, ...list));
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
propertyAnnotations(type, entity) {
|
|
203
|
+
for (const property of type.properties) {
|
|
204
|
+
const field = entity.fields.find((f) => f.name === property.field);
|
|
205
|
+
if (field === undefined) {
|
|
206
|
+
continue;
|
|
207
|
+
}
|
|
208
|
+
const a = field.annotations;
|
|
209
|
+
const children = [];
|
|
210
|
+
const label = str(getAnnotation(a, "EndUserText.label"));
|
|
211
|
+
if (label !== undefined)
|
|
212
|
+
children.push(annotation("Common.Label", { String: label }));
|
|
213
|
+
const quickInfo = str(getAnnotation(a, "EndUserText.quickInfo"));
|
|
214
|
+
if (quickInfo !== undefined)
|
|
215
|
+
children.push(annotation("Common.QuickInfo", { String: quickInfo }));
|
|
216
|
+
if (property.computed)
|
|
217
|
+
children.push(annotation("Core.Computed"));
|
|
218
|
+
// D57: multi-line texts, e-mail addresses and phone numbers, default filter values
|
|
219
|
+
if (getAnnotation(a, "UI.multiLineText") === true)
|
|
220
|
+
children.push(annotation("UI.MultiLineText"));
|
|
221
|
+
if (getAnnotation(a, "Semantics.email.address") === true)
|
|
222
|
+
children.push(annotation("Communication.IsEmailAddress"));
|
|
223
|
+
if (getAnnotation(a, "Semantics.telephone.type") !== undefined)
|
|
224
|
+
children.push(annotation("Communication.IsPhoneNumber"));
|
|
225
|
+
const filterDefault = getAnnotation(a, "Consumption.filter.defaultValue");
|
|
226
|
+
if (typeof filterDefault === "string" || typeof filterDefault === "number")
|
|
227
|
+
children.push(annotation("Common.FilterDefaultValue", { String: String(filterDefault) }));
|
|
228
|
+
if (property.immutable)
|
|
229
|
+
children.push(annotation("Core.Immutable"));
|
|
230
|
+
if (property.dynamic) {
|
|
231
|
+
children.push(annotation("Common.FieldControl", { Path: `${FIELD_CONTROL}/${property.name}` }));
|
|
232
|
+
}
|
|
233
|
+
else if (property.mandatory) {
|
|
234
|
+
children.push(annotation("Common.FieldControl", { EnumMember: "Common.FieldControlType/Mandatory" }));
|
|
235
|
+
}
|
|
236
|
+
if (property.edm.digitSequence === true && property.edm.enum === undefined)
|
|
237
|
+
children.push(annotation("Common.IsDigitSequence"));
|
|
238
|
+
if (field.type.kind === "cuky")
|
|
239
|
+
children.push(annotation("Common.IsCurrency"));
|
|
240
|
+
if (field.type.kind === "unit")
|
|
241
|
+
children.push(annotation("Common.IsUnit"));
|
|
242
|
+
if (field.currencyCode !== undefined)
|
|
243
|
+
children.push(annotation("Measures.ISOCurrency", { Path: field.currencyCode }));
|
|
244
|
+
if (field.unitOfMeasure !== undefined)
|
|
245
|
+
children.push(annotation("Measures.Unit", { Path: field.unitOfMeasure }));
|
|
246
|
+
if (getAnnotation(a, "UI.hidden") === true)
|
|
247
|
+
children.push(annotation("UI.Hidden"));
|
|
248
|
+
const help = valueHelpOf(this.model, field);
|
|
249
|
+
if (help !== undefined) {
|
|
250
|
+
children.push(annotation("Common.ValueList", {}, record("Common.ValueListType", pv("Label", { String: help.label }), pv("CollectionPath", { String: help.set }), pv("SearchSupported", { Bool: this.service.entitySets.find((s) => s.name === help.set)?.searchable === true }), pv("Parameters", {}, collection(record("Common.ValueListParameterInOut", pv("LocalDataProperty", { PropertyPath: property.name }), pv("ValueListProperty", { String: help.element })),
|
|
251
|
+
// additionalBinding: In (filter), Out (result), InOut (both)
|
|
252
|
+
...(help.bindings ?? []).map((b) => record(b.usage === "filter" ? "Common.ValueListParameterIn" : b.usage === "result" ? "Common.ValueListParameterOut" : "Common.ValueListParameterInOut", pv("LocalDataProperty", { PropertyPath: b.local }), pv("ValueListProperty", { String: b.element }))), ...help.columns
|
|
253
|
+
.slice(1)
|
|
254
|
+
.filter((c) => !(help.bindings ?? []).some((b) => b.element === c.property))
|
|
255
|
+
.map((c) => record("Common.ValueListParameterDisplayOnly", pv("ValueListProperty", { String: c.property }))))))));
|
|
256
|
+
if (help.validation === true)
|
|
257
|
+
children.push(annotation("Common.ValueListForValidation", { String: "" }));
|
|
258
|
+
if (help.fixedValues === true)
|
|
259
|
+
children.push(annotation("Common.ValueListWithFixedValues"));
|
|
260
|
+
}
|
|
261
|
+
const text = getArray(a, "ObjectModel.text.element")?.map(str).find((t) => t !== undefined);
|
|
262
|
+
if (text !== undefined) {
|
|
263
|
+
const arrangement = str(getAnnotation(a, "UI.textArrangement"));
|
|
264
|
+
children.push(annotation("Common.Text", { Path: text }, arrangement === undefined ? undefined : annotation("UI.TextArrangement", { EnumMember: `UI.TextArrangementType/${enumName(arrangement)}` })));
|
|
265
|
+
}
|
|
266
|
+
this.annotations(`${this.qualified(type.name)}/${property.name}`, children);
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
/** `@UI.dataPoint` of the elements: UI.DataPoint, qualified by its qualifier or the element name (D57). */
|
|
270
|
+
dataPoints(entity) {
|
|
271
|
+
const result = [];
|
|
272
|
+
for (const field of entity.fields) {
|
|
273
|
+
const point = getAnnotation(field.annotations, "UI.dataPoint");
|
|
274
|
+
if (!isObject(point))
|
|
275
|
+
continue;
|
|
276
|
+
const title = str(point["title"]) ?? str(getAnnotation(field.annotations, "EndUserText.label"));
|
|
277
|
+
const criticality = str(point["criticality"]);
|
|
278
|
+
const target = point["targetvalue"];
|
|
279
|
+
const visualization = str(point["visualization"]);
|
|
280
|
+
result.push(annotation("UI.DataPoint", { Qualifier: str(point["qualifier"]) ?? field.name }, record("UI.DataPointType", pv("Value", { Path: field.name }), title === undefined ? undefined : pv("Title", { String: title }), str(point["description"]) === undefined ? undefined : pv("Description", { String: str(point["description"]) }), typeof target === "number" ? pv("TargetValue", { Decimal: target }) : undefined, criticality === undefined ? undefined : pv("Criticality", { Path: criticality }), visualization === undefined ? undefined : pv("Visualization", { EnumMember: `UI.VisualizationType/${enumName(visualization)}` }))));
|
|
281
|
+
}
|
|
282
|
+
return result;
|
|
283
|
+
}
|
|
284
|
+
dataFields(entity, path) {
|
|
285
|
+
const result = [];
|
|
286
|
+
for (const { field, entry } of entriesOf(entity, path)) {
|
|
287
|
+
const kind = str(entry["type"]) ?? "#STANDARD";
|
|
288
|
+
const label = str(entry["label"]);
|
|
289
|
+
const importance = str(entry["importance"]);
|
|
290
|
+
const importanceAnnotation = importance === undefined ? undefined : annotation("UI.Importance", { EnumMember: `UI.ImportanceType/${enumName(importance)}` });
|
|
291
|
+
const hidden = entry["hidden"] === true ? annotation("UI.Hidden") : undefined;
|
|
292
|
+
if (kind === "#FOR_ACTION") {
|
|
293
|
+
const action = str(entry["dataaction"]);
|
|
294
|
+
if (action === undefined) {
|
|
295
|
+
continue;
|
|
296
|
+
}
|
|
297
|
+
const grouping = str(entry["invocationgrouping"]);
|
|
298
|
+
result.push(record("UI.DataFieldForAction", pv("Action", { String: `${this.service.namespace}.${action}` }), label === undefined ? undefined : pv("Label", { String: label }), grouping === undefined ? undefined : pv("InvocationGrouping", { EnumMember: `UI.OperationGroupingType/${enumName(grouping)}` }), entry["inline"] === true ? pv("Inline", { Bool: true }) : undefined, importanceAnnotation, hidden));
|
|
299
|
+
continue;
|
|
300
|
+
}
|
|
301
|
+
const criticality = str(entry["criticality"]);
|
|
302
|
+
const url = str(entry["url"]);
|
|
303
|
+
if (kind === "#AS_DATAPOINT") {
|
|
304
|
+
const point = getAnnotation(entity.fields.find((f) => f.name === field)?.annotations, "UI.dataPoint");
|
|
305
|
+
const qualifier = isObject(point) ? (str(point["qualifier"]) ?? field) : field;
|
|
306
|
+
result.push(record("UI.DataFieldForAnnotation", pv("Target", { AnnotationPath: `@UI.DataPoint#${qualifier}` }), label === undefined ? undefined : pv("Label", { String: label }), importanceAnnotation, hidden));
|
|
307
|
+
continue;
|
|
308
|
+
}
|
|
309
|
+
result.push(record(kind === "#WITH_URL" && url !== undefined ? "UI.DataFieldWithUrl" : "UI.DataField", pv("Value", { Path: field }), label === undefined ? undefined : pv("Label", { String: label }), kind === "#WITH_URL" && url !== undefined ? pv("Url", { Path: url }) : undefined, criticality === undefined ? undefined : pv("Criticality", { Path: criticality }), importanceAnnotation, hidden));
|
|
310
|
+
}
|
|
311
|
+
return result;
|
|
312
|
+
}
|
|
313
|
+
typeAnnotations(type, entity) {
|
|
314
|
+
const a = entity.annotations;
|
|
315
|
+
const children = [];
|
|
316
|
+
const header = getAnnotation(a, "UI.headerInfo");
|
|
317
|
+
if (isObject(header)) {
|
|
318
|
+
const dataField = (value) => {
|
|
319
|
+
if (!isObject(value)) {
|
|
320
|
+
return undefined;
|
|
321
|
+
}
|
|
322
|
+
const path = str(value["value"]);
|
|
323
|
+
return path === undefined ? undefined : record("UI.DataField", pv("Value", { Path: path }), str(value["label"]) === undefined ? undefined : pv("Label", { String: str(value["label"]) }));
|
|
324
|
+
};
|
|
325
|
+
const title = dataField(header["title"]);
|
|
326
|
+
const description = dataField(header["description"]);
|
|
327
|
+
children.push(annotation("UI.HeaderInfo", {}, record("UI.HeaderInfoType", pv("TypeName", { String: str(header["typename"]) ?? type.name.replace(/Type$/, "") }), pv("TypeNamePlural", { String: str(header["typenameplural"]) ?? type.name.replace(/Type$/, "") }), title === undefined ? undefined : pv("Title", {}, title), description === undefined ? undefined : pv("Description", {}, description), str(header["imageurl"]) === undefined ? undefined : pv("ImageUrl", { Path: str(header["imageurl"]) }))));
|
|
328
|
+
}
|
|
329
|
+
const lineItem = this.dataFields(entity, "UI.lineItem");
|
|
330
|
+
if (lineItem.length > 0)
|
|
331
|
+
children.push(annotation("UI.LineItem", {}, collection(...lineItem)));
|
|
332
|
+
const identification = this.dataFields(entity, "UI.identification");
|
|
333
|
+
if (identification.length > 0)
|
|
334
|
+
children.push(annotation("UI.Identification", {}, collection(...identification)));
|
|
335
|
+
const selection = entriesOf(entity, "UI.selectionField");
|
|
336
|
+
if (selection.length > 0)
|
|
337
|
+
children.push(annotation("UI.SelectionFields", {}, collection(...selection.map((s) => propertyPath(s.field)))));
|
|
338
|
+
const groups = new Map();
|
|
339
|
+
for (const item of entriesOf(entity, "UI.fieldGroup")) {
|
|
340
|
+
const qualifier = str(item.entry["qualifier"]) ?? "";
|
|
341
|
+
groups.set(qualifier, [...(groups.get(qualifier) ?? []), item]);
|
|
342
|
+
}
|
|
343
|
+
for (const [qualifier, items] of groups) {
|
|
344
|
+
const fields = items.map((i) => record("UI.DataField", pv("Value", { Path: i.field }), str(i.entry["label"]) === undefined ? undefined : pv("Label", { String: str(i.entry["label"]) })));
|
|
345
|
+
children.push(annotation("UI.FieldGroup", { Qualifier: qualifier === "" ? undefined : qualifier }, record("UI.FieldGroupType", pv("Data", {}, collection(...fields)))));
|
|
346
|
+
}
|
|
347
|
+
children.push(...this.facets(entity));
|
|
348
|
+
children.push(...this.dataPoints(entity));
|
|
349
|
+
const variant = getArray(a, "UI.presentationVariant")?.find(isObject);
|
|
350
|
+
if (variant !== undefined) {
|
|
351
|
+
const sortOrder = (getArray(variant, "sortOrder") ?? []).filter(isObject);
|
|
352
|
+
children.push(annotation("UI.PresentationVariant", {}, record("UI.PresentationVariantType", sortOrder.length === 0
|
|
353
|
+
? undefined
|
|
354
|
+
: pv("SortOrder", {}, collection(...sortOrder.map((s) => record("Common.SortOrderType", pv("Property", { PropertyPath: str(s["by"]) ?? "" }), pv("Descending", { Bool: str(s["direction"]) === "#DESC" }))))), pv("Visualizations", {}, collection(el("AnnotationPath", undefined, "@UI.LineItem"))))));
|
|
355
|
+
}
|
|
356
|
+
const semanticKey = getArray(a, "ObjectModel.semanticKey")?.map(str).filter((k) => k !== undefined);
|
|
357
|
+
if (semanticKey !== undefined && semanticKey.length > 0) {
|
|
358
|
+
children.push(annotation("Common.SemanticKey", {}, collection(...semanticKey.map(propertyPath))));
|
|
359
|
+
}
|
|
360
|
+
children.push(...this.sideEffects(type, entity));
|
|
361
|
+
const label = str(getAnnotation(a, "EndUserText.label"));
|
|
362
|
+
if (label !== undefined)
|
|
363
|
+
children.push(annotation("Common.Label", { String: label }));
|
|
364
|
+
this.annotations(this.qualified(type.name), children);
|
|
365
|
+
}
|
|
366
|
+
facets(entity) {
|
|
367
|
+
const all = [];
|
|
368
|
+
for (const field of entity.fields) {
|
|
369
|
+
for (const facet of getArray(field.annotations, "UI.facet") ?? []) {
|
|
370
|
+
if (isObject(facet)) {
|
|
371
|
+
all.push(facet);
|
|
372
|
+
}
|
|
373
|
+
}
|
|
374
|
+
}
|
|
375
|
+
if (all.length === 0) {
|
|
376
|
+
return [];
|
|
377
|
+
}
|
|
378
|
+
const byPosition = (x, y) => num(x["position"]) - num(y["position"]);
|
|
379
|
+
const render = (facet) => {
|
|
380
|
+
const type = str(facet["type"]) ?? "#COLLECTION";
|
|
381
|
+
const id = str(facet["id"]);
|
|
382
|
+
const label = str(facet["label"]);
|
|
383
|
+
const common = [id === undefined ? undefined : pv("ID", { String: id }), label === undefined ? undefined : pv("Label", { String: label })];
|
|
384
|
+
const hidden = facet["hidden"] === true ? annotation("UI.Hidden") : undefined;
|
|
385
|
+
if (type === "#COLLECTION") {
|
|
386
|
+
const children = all.filter((f) => str(f["parentid"]) === id && id !== undefined).sort(byPosition).map(render);
|
|
387
|
+
return record("UI.CollectionFacet", ...common, pv("Facets", {}, collection(...children)), hidden);
|
|
388
|
+
}
|
|
389
|
+
const qualifier = str(facet["targetqualifier"]);
|
|
390
|
+
const suffix = qualifier === undefined ? "" : `#${qualifier}`;
|
|
391
|
+
let target;
|
|
392
|
+
switch (type) {
|
|
393
|
+
case "#IDENTIFICATION_REFERENCE":
|
|
394
|
+
target = `@UI.Identification${suffix}`;
|
|
395
|
+
break;
|
|
396
|
+
case "#FIELDGROUP_REFERENCE":
|
|
397
|
+
target = `@UI.FieldGroup${suffix}`;
|
|
398
|
+
break;
|
|
399
|
+
case "#LINEITEM_REFERENCE": {
|
|
400
|
+
const element = str(facet["targetelement"]);
|
|
401
|
+
target = element === undefined ? `@UI.LineItem${suffix}` : `${element}/@UI.LineItem${suffix}`;
|
|
402
|
+
break;
|
|
403
|
+
}
|
|
404
|
+
case "#DATAPOINT_REFERENCE":
|
|
405
|
+
target = `@UI.DataPoint${suffix}`;
|
|
406
|
+
break;
|
|
407
|
+
case "#PRESENTATIONVARIANT_REFERENCE":
|
|
408
|
+
target = `@UI.PresentationVariant${suffix}`;
|
|
409
|
+
break;
|
|
410
|
+
default:
|
|
411
|
+
return undefined;
|
|
412
|
+
}
|
|
413
|
+
return record("UI.ReferenceFacet", ...common, pv("Target", { AnnotationPath: target }), hidden);
|
|
414
|
+
};
|
|
415
|
+
const topLevel = (purpose) => all
|
|
416
|
+
.filter((f) => str(f["parentid"]) === undefined && (str(f["purpose"]) ?? "#STANDARD") === purpose)
|
|
417
|
+
.sort(byPosition)
|
|
418
|
+
.map(render)
|
|
419
|
+
.filter((x) => x !== undefined);
|
|
420
|
+
const result = [];
|
|
421
|
+
const standard = topLevel("#STANDARD");
|
|
422
|
+
if (standard.length > 0)
|
|
423
|
+
result.push(annotation("UI.Facets", {}, collection(...standard)));
|
|
424
|
+
const header = topLevel("#HEADER");
|
|
425
|
+
if (header.length > 0)
|
|
426
|
+
result.push(annotation("UI.HeaderFacets", {}, collection(...header)));
|
|
427
|
+
return result;
|
|
428
|
+
}
|
|
429
|
+
/** BDEF side effects of the base entity, renamed through the projection. */
|
|
430
|
+
sideEffects(type, entity) {
|
|
431
|
+
const behavior = exposedBehavior(this.model, entity).base;
|
|
432
|
+
const result = [];
|
|
433
|
+
let index = 0;
|
|
434
|
+
for (const effect of behavior?.sideEffects ?? []) {
|
|
435
|
+
if (effect.trigger.kind !== "field") {
|
|
436
|
+
continue;
|
|
437
|
+
}
|
|
438
|
+
const source = projectedFieldName(this.model, entity, effect.trigger.name);
|
|
439
|
+
if (source === undefined) {
|
|
440
|
+
continue;
|
|
441
|
+
}
|
|
442
|
+
const targets = this.sideEffectTargets(type, entity, effect.targets, "");
|
|
443
|
+
if (targets.properties.length + targets.entities.length === 0) {
|
|
444
|
+
continue;
|
|
445
|
+
}
|
|
446
|
+
index++;
|
|
447
|
+
result.push(annotation("Common.SideEffects", { Qualifier: `${source}${index > 1 ? index : ""}` }, record("Common.SideEffectsType", pv("SourceProperties", {}, collection(propertyPath(source))), targets.properties.length === 0 ? undefined : pv("TargetProperties", {}, collection(...targets.properties.map((p) => el("String", undefined, p)))), targets.entities.length === 0 ? undefined : pv("TargetEntities", {}, collection(...targets.entities.map((p) => el("NavigationPropertyPath", undefined, p)))))));
|
|
448
|
+
}
|
|
449
|
+
return result;
|
|
450
|
+
}
|
|
451
|
+
sideEffectTargets(type, entity, targets, prefix) {
|
|
452
|
+
const properties = [];
|
|
453
|
+
const entities = [];
|
|
454
|
+
for (const target of targets) {
|
|
455
|
+
const segments = target.path.split(".");
|
|
456
|
+
if (target.kind === "field" && segments.length === 1) {
|
|
457
|
+
const projected = projectedFieldName(this.model, entity, segments[0] ?? "");
|
|
458
|
+
if (projected !== undefined)
|
|
459
|
+
properties.push(`${prefix}${projected}`);
|
|
460
|
+
}
|
|
461
|
+
else if (target.kind === "field" && segments.length === 2) {
|
|
462
|
+
const navigation = type.navigations.find((n) => same(n.name, segments[0] ?? ""));
|
|
463
|
+
const targetEntity = navigation === undefined ? undefined : findEntity(this.model, navigation.targetEntity);
|
|
464
|
+
const projected = targetEntity === undefined ? undefined : projectedFieldName(this.model, targetEntity, segments[1] ?? "");
|
|
465
|
+
if (navigation !== undefined && projected !== undefined)
|
|
466
|
+
properties.push(`${prefix}${navigation.name}/${projected}`);
|
|
467
|
+
}
|
|
468
|
+
else if (target.kind === "entity") {
|
|
469
|
+
const navigation = type.navigations.find((n) => same(n.name, target.path));
|
|
470
|
+
if (navigation !== undefined)
|
|
471
|
+
entities.push(`${prefix}${navigation.name}`);
|
|
472
|
+
else if (target.path === "$self" || target.path === "")
|
|
473
|
+
entities.push(prefix === "" ? "" : prefix.replace(/\/$/, ""));
|
|
474
|
+
}
|
|
475
|
+
}
|
|
476
|
+
return { properties, entities };
|
|
477
|
+
}
|
|
478
|
+
/** `@Consumption.filter.mandatory` and `selectionType` / `multipleSelections` (D57). */
|
|
479
|
+
filterRestrictions(type) {
|
|
480
|
+
const entity = findEntity(this.model, type.entity);
|
|
481
|
+
if (entity === undefined)
|
|
482
|
+
return undefined;
|
|
483
|
+
const exposed = entity.fields.filter((f) => type.properties.some((p) => p.name === f.name));
|
|
484
|
+
const required = exposed.filter((f) => getAnnotation(f.annotations, "Consumption.filter.mandatory") === true).map((f) => f.name);
|
|
485
|
+
const expressions = exposed.flatMap((f) => {
|
|
486
|
+
const allowed = allowedFilterExpressions(f.annotations);
|
|
487
|
+
return allowed === undefined ? [] : [{ property: f.name, allowed }];
|
|
488
|
+
});
|
|
489
|
+
if (required.length === 0 && expressions.length === 0)
|
|
490
|
+
return undefined;
|
|
491
|
+
return annotation("Capabilities.FilterRestrictions", {}, record("Capabilities.FilterRestrictionsType", required.length === 0 ? undefined : pv("RequiredProperties", {}, collection(...required.map(propertyPath))), expressions.length === 0
|
|
492
|
+
? undefined
|
|
493
|
+
: pv("FilterExpressionRestrictions", {}, collection(...expressions.map((e) => record("Capabilities.FilterExpressionRestrictionType", pv("Property", { PropertyPath: e.property }), pv("AllowedExpressions", { String: e.allowed })))))));
|
|
494
|
+
}
|
|
495
|
+
setAnnotations(type) {
|
|
496
|
+
const set = this.service.entitySets.find((s) => s.entityType === type.name);
|
|
497
|
+
if (set === undefined) {
|
|
498
|
+
return;
|
|
499
|
+
}
|
|
500
|
+
const children = [
|
|
501
|
+
annotation("Capabilities.InsertRestrictions", {}, record("Capabilities.InsertRestrictionsType", pv("Insertable", { Bool: set.insertable }))),
|
|
502
|
+
annotation("Capabilities.UpdateRestrictions", {}, record("Capabilities.UpdateRestrictionsType", pv("Updatable", type.entityControl?.updatable === true && set.updatable ? { Path: `${ENTITY_CONTROL}/Updatable` } : { Bool: set.updatable }))),
|
|
503
|
+
annotation("Capabilities.DeleteRestrictions", {}, record("Capabilities.DeleteRestrictionsType", pv("Deletable", type.entityControl?.deletable === true && set.deletable ? { Path: `${ENTITY_CONTROL}/Deletable` } : { Bool: set.deletable }))),
|
|
504
|
+
annotation("Capabilities.SearchRestrictions", {}, record("Capabilities.SearchRestrictionsType", pv("Searchable", { Bool: set.searchable }))),
|
|
505
|
+
this.filterRestrictions(type),
|
|
506
|
+
];
|
|
507
|
+
const navigations = type.navigations.filter((n) => n.collection);
|
|
508
|
+
if (navigations.length > 0) {
|
|
509
|
+
children.push(annotation("Capabilities.NavigationRestrictions", {}, record("Capabilities.NavigationRestrictionsType", pv("RestrictedProperties", {}, collection(...navigations.map((n) => record("Capabilities.NavigationPropertyRestriction", pv("NavigationProperty", { NavigationPropertyPath: n.name }), pv("InsertRestrictions", {}, record("Capabilities.InsertRestrictionsType", pv("Insertable", n.insertableDynamic ? { Path: `${OPERATION_CONTROL}/${n.name}` } : { Bool: n.insertable }))))))))));
|
|
510
|
+
}
|
|
511
|
+
if (type.etag !== undefined) {
|
|
512
|
+
children.push(annotation("Core.OptimisticConcurrency", {}, collection(propertyPath(type.etag))));
|
|
513
|
+
}
|
|
514
|
+
else if (type.etagPath !== undefined) {
|
|
515
|
+
// etag dependent by: the ETag property of the master, through the to-one navigations
|
|
516
|
+
children.push(annotation("Core.OptimisticConcurrency", {}, collection(propertyPath([...type.etagPath.navigations, type.etagPath.property].join("/")))));
|
|
517
|
+
}
|
|
518
|
+
// draft (D54): the root and the nodes of a draft BO, with the actions of the draft
|
|
519
|
+
if (set.draft?.root === true) {
|
|
520
|
+
const action = (name) => `${this.service.namespace}.${name}`;
|
|
521
|
+
children.push(annotation("Common.DraftRoot", {}, record("Common.DraftRootType", pv("ActivationAction", { String: action("Activate") }), pv("EditAction", { String: action("Edit") }), pv("PreparationAction", { String: action("Prepare") }), pv("DiscardAction", { String: action("Discard") }), pv("ResumeAction", { String: action("Resume") }), set.draft.collaborative === true ? pv("ShareAction", { String: action("Share") }) : undefined)));
|
|
522
|
+
}
|
|
523
|
+
else if (set.draft !== undefined) {
|
|
524
|
+
children.push(annotation("Common.DraftNode", {}, record("Common.DraftNodeType", pv("PreparationAction", { String: `${this.service.namespace}.Prepare` }))));
|
|
525
|
+
}
|
|
526
|
+
this.annotations(`${SCHEMA_ALIAS}.${CONTAINER_NAME}/${set.name}`, children);
|
|
527
|
+
}
|
|
528
|
+
operationAnnotations(operation) {
|
|
529
|
+
const target = `${this.qualified(operation.name)}(${this.bindingType(operation)})`;
|
|
530
|
+
const children = [];
|
|
531
|
+
if (operation.dynamic && !operation.collectionBound) {
|
|
532
|
+
children.push(annotation("Core.OperationAvailable", { Path: `_it/${OPERATION_CONTROL}/${operation.name}` }));
|
|
533
|
+
}
|
|
534
|
+
const type = this.service.entityTypes.find((t) => t.name === operation.boundType);
|
|
535
|
+
const entity = type === undefined ? undefined : findEntity(this.model, type.entity);
|
|
536
|
+
const behavior = entity === undefined ? undefined : exposedBehavior(this.model, entity).base;
|
|
537
|
+
for (const effect of behavior?.sideEffects.filter((e) => e.trigger.kind === "action" && same(e.trigger.name, operation.action)) ?? []) {
|
|
538
|
+
if (type === undefined || entity === undefined) {
|
|
539
|
+
break;
|
|
540
|
+
}
|
|
541
|
+
const targets = this.sideEffectTargets(type, entity, effect.targets, "_it/");
|
|
542
|
+
if (targets.properties.length + targets.entities.length > 0) {
|
|
543
|
+
children.push(annotation("Common.SideEffects", {}, record("Common.SideEffectsType", targets.properties.length === 0 ? undefined : pv("TargetProperties", {}, collection(...targets.properties.map((p) => el("String", undefined, p)))), targets.entities.length === 0 ? undefined : pv("TargetEntities", {}, collection(...targets.entities.map((p) => el("NavigationPropertyPath", undefined, p)))))));
|
|
544
|
+
}
|
|
545
|
+
}
|
|
546
|
+
this.annotations(target, children);
|
|
547
|
+
// parameter labels
|
|
548
|
+
const action = behavior?.actions.find((a) => same(a.name, operation.action));
|
|
549
|
+
const parameterEntity = action?.parameter === undefined ? undefined : findEntity(this.model, action.parameter);
|
|
550
|
+
for (const parameter of operation.parameters) {
|
|
551
|
+
const field = parameterEntity?.fields.find((f) => f.name === parameter.name);
|
|
552
|
+
const label = str(getAnnotation(field?.annotations, "EndUserText.label"));
|
|
553
|
+
if (label !== undefined) {
|
|
554
|
+
this.annotations(`${target}/${parameter.name}`, [annotation("Common.Label", { String: label })]);
|
|
555
|
+
}
|
|
556
|
+
}
|
|
557
|
+
}
|
|
558
|
+
}
|
|
559
|
+
export function generateCsdl(model, service = buildODataService(model)) {
|
|
560
|
+
return new CsdlBuilder(model, service).build();
|
|
561
|
+
}
|
|
562
|
+
//# sourceMappingURL=csdl.js.map
|