@linkup-ai/abap-ai 2.0.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/README.md +384 -0
- package/dist/adt-client.js +364 -0
- package/dist/cli/activate.js +113 -0
- package/dist/cli/init.js +333 -0
- package/dist/cli/remove.js +80 -0
- package/dist/cli/status.js +229 -0
- package/dist/cli/systems.js +68 -0
- package/dist/cli.js +81 -0
- package/dist/index.js +1318 -0
- package/dist/knowledge/abap/abap-dictionary.md +199 -0
- package/dist/knowledge/abap/abap-sql.md +296 -0
- package/dist/knowledge/abap/amdp.md +273 -0
- package/dist/knowledge/abap/clean-code.md +293 -0
- package/dist/knowledge/abap/cloud-background-processing.md +250 -0
- package/dist/knowledge/abap/cloud-communication.md +265 -0
- package/dist/knowledge/abap/cloud-development.md +176 -0
- package/dist/knowledge/abap/cloud-extensibility.md +252 -0
- package/dist/knowledge/abap/cloud-released-apis.md +261 -0
- package/dist/knowledge/abap/constructor-expressions.md +289 -0
- package/dist/knowledge/abap/enhancements.md +232 -0
- package/dist/knowledge/abap/exceptions.md +271 -0
- package/dist/knowledge/abap/internal-tables.md +205 -0
- package/dist/knowledge/abap/object-orientation.md +298 -0
- package/dist/knowledge/abap/performance.md +216 -0
- package/dist/knowledge/abap/rap-abstract-entities.md +206 -0
- package/dist/knowledge/abap/rap-business-events.md +216 -0
- package/dist/knowledge/abap/rap-draft.md +191 -0
- package/dist/knowledge/abap/rap-eml.md +453 -0
- package/dist/knowledge/abap/rap-end-to-end.md +486 -0
- package/dist/knowledge/abap/rap-feature-control.md +185 -0
- package/dist/knowledge/abap/rap-numbering.md +280 -0
- package/dist/knowledge/abap/rap-service-exposure.md +163 -0
- package/dist/knowledge/abap/rap-unmanaged.md +468 -0
- package/dist/knowledge/abap/string-processing.md +180 -0
- package/dist/knowledge/abap/unit-testing.md +303 -0
- package/dist/knowledge/abap-cds/access-control.md +241 -0
- package/dist/knowledge/abap-cds/annotations.md +331 -0
- package/dist/knowledge/abap-cds/associations.md +254 -0
- package/dist/knowledge/abap-cds/expressions.md +230 -0
- package/dist/knowledge/abap-cds/functions.md +245 -0
- package/dist/knowledge/abap-cds/metadata-extensions.md +294 -0
- package/dist/knowledge/cap/authentication.md +278 -0
- package/dist/knowledge/cap/cdl-syntax.md +247 -0
- package/dist/knowledge/cap/cql-queries.md +266 -0
- package/dist/knowledge/cap/deployment.md +343 -0
- package/dist/knowledge/cap/event-handlers.md +287 -0
- package/dist/knowledge/cap/fiori-integration.md +303 -0
- package/dist/knowledge/cap/service-definitions.md +287 -0
- package/dist/knowledge/fiori/annotations.md +347 -0
- package/dist/knowledge/fiori/deployment.md +340 -0
- package/dist/knowledge/fiori/fiori-elements.md +332 -0
- package/dist/knowledge/fiori/fiori-side-effects.md +107 -0
- package/dist/knowledge/fiori/fiori-valuelist.md +144 -0
- package/dist/knowledge/fiori/ui5-controllers.md +358 -0
- package/dist/knowledge/fiori/ui5-data-binding.md +311 -0
- package/dist/knowledge/fiori/ui5-fragments-dialogs.md +330 -0
- package/dist/knowledge/fiori/ui5-manifest.md +411 -0
- package/dist/knowledge/fiori/ui5-routing.md +303 -0
- package/dist/knowledge/fiori/ui5-xml-views.md +294 -0
- package/dist/logger.js +114 -0
- package/dist/system-profile.js +207 -0
- package/dist/tools/abap-doc.js +72 -0
- package/dist/tools/abapgit.js +161 -0
- package/dist/tools/activate.js +68 -0
- package/dist/tools/atc-check.js +117 -0
- package/dist/tools/auth-object.js +56 -0
- package/dist/tools/breakpoints.js +76 -0
- package/dist/tools/call-hierarchy.js +84 -0
- package/dist/tools/cds-annotations.js +98 -0
- package/dist/tools/cds-dependencies.js +65 -0
- package/dist/tools/check.js +47 -0
- package/dist/tools/code-completion.js +70 -0
- package/dist/tools/code-coverage.js +111 -0
- package/dist/tools/create-amdp.js +111 -0
- package/dist/tools/create-dcl.js +81 -0
- package/dist/tools/create-transport.js +38 -0
- package/dist/tools/create.js +285 -0
- package/dist/tools/data-preview.js +37 -0
- package/dist/tools/delete.js +45 -0
- package/dist/tools/deploy-bsp.js +298 -0
- package/dist/tools/discovery.js +59 -0
- package/dist/tools/element-info.js +93 -0
- package/dist/tools/enhancements.js +186 -0
- package/dist/tools/extract-method.js +44 -0
- package/dist/tools/function-group.js +59 -0
- package/dist/tools/knowledge.js +275 -0
- package/dist/tools/lock-object.js +75 -0
- package/dist/tools/message-class.js +67 -0
- package/dist/tools/navigate.js +80 -0
- package/dist/tools/number-range.js +57 -0
- package/dist/tools/object-documentation.js +43 -0
- package/dist/tools/object-structure.js +78 -0
- package/dist/tools/object-versions.js +57 -0
- package/dist/tools/package-contents.js +60 -0
- package/dist/tools/pretty-printer.js +35 -0
- package/dist/tools/publish-binding.js +49 -0
- package/dist/tools/quick-fix.js +69 -0
- package/dist/tools/read.js +167 -0
- package/dist/tools/refactor-rename.js +60 -0
- package/dist/tools/release-transport.js +24 -0
- package/dist/tools/released-apis.js +51 -0
- package/dist/tools/repository-tree.js +90 -0
- package/dist/tools/scaffold-rap.js +642 -0
- package/dist/tools/search.js +73 -0
- package/dist/tools/shared/data-format.js +101 -0
- package/dist/tools/sql-console.js +17 -0
- package/dist/tools/system-info.js +270 -0
- package/dist/tools/traces.js +66 -0
- package/dist/tools/transport-contents.js +83 -0
- package/dist/tools/transports.js +67 -0
- package/dist/tools/unit-test.js +135 -0
- package/dist/tools/where-used.js +59 -0
- package/dist/tools/write.js +101 -0
- package/package.json +49 -0
|
@@ -0,0 +1,303 @@
|
|
|
1
|
+
# Fiori Integration — CAP UI Annotations and Drafts
|
|
2
|
+
|
|
3
|
+
## Enable Drafts
|
|
4
|
+
|
|
5
|
+
```cds
|
|
6
|
+
// Enable on the SERVICE projection, not the base entity
|
|
7
|
+
service AdminService {
|
|
8
|
+
entity Books as projection on my.Books;
|
|
9
|
+
}
|
|
10
|
+
annotate AdminService.Books with @odata.draft.enabled;
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
## HeaderInfo
|
|
14
|
+
|
|
15
|
+
```cds
|
|
16
|
+
annotate CatalogService.Books with @UI.HeaderInfo: {
|
|
17
|
+
TypeName : 'Book',
|
|
18
|
+
TypeNamePlural : 'Books',
|
|
19
|
+
Title : { Value: title },
|
|
20
|
+
Description : { Value: author.name },
|
|
21
|
+
ImageUrl : coverImageUrl,
|
|
22
|
+
TypeImageUrl : 'sap-icon://education'
|
|
23
|
+
};
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
## SelectionFields (Filter Bar)
|
|
27
|
+
|
|
28
|
+
```cds
|
|
29
|
+
annotate CatalogService.Books with @UI.SelectionFields: [
|
|
30
|
+
title,
|
|
31
|
+
author_ID,
|
|
32
|
+
genre_code,
|
|
33
|
+
price
|
|
34
|
+
];
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
## LineItem (Table Columns)
|
|
38
|
+
|
|
39
|
+
```cds
|
|
40
|
+
annotate CatalogService.Books with @UI.LineItem: [
|
|
41
|
+
{ Value: title, Label: 'Title' },
|
|
42
|
+
{ Value: author.name, Label: 'Author' },
|
|
43
|
+
{ Value: stock, Label: 'In Stock' },
|
|
44
|
+
{ Value: price, Label: 'Price' },
|
|
45
|
+
{
|
|
46
|
+
$Type: 'UI.DataFieldForAction',
|
|
47
|
+
Action: 'CatalogService.order',
|
|
48
|
+
Label: 'Order',
|
|
49
|
+
Inline: true
|
|
50
|
+
},
|
|
51
|
+
{
|
|
52
|
+
$Type: 'UI.DataFieldForAnnotation',
|
|
53
|
+
Target: '@UI.DataPoint#rating',
|
|
54
|
+
Label: 'Rating'
|
|
55
|
+
}
|
|
56
|
+
];
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
## DataPoint
|
|
60
|
+
|
|
61
|
+
```cds
|
|
62
|
+
annotate CatalogService.Books with @UI.DataPoint#rating: {
|
|
63
|
+
Value: rating,
|
|
64
|
+
Visualization: #Rating,
|
|
65
|
+
TargetValue: 5
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
annotate CatalogService.Books with @UI.DataPoint#stock: {
|
|
69
|
+
Value: stock,
|
|
70
|
+
Criticality: stockCriticality
|
|
71
|
+
};
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
## Facets (Object Page Sections)
|
|
75
|
+
|
|
76
|
+
```cds
|
|
77
|
+
annotate CatalogService.Books with @UI.Facets: [
|
|
78
|
+
{
|
|
79
|
+
$Type : 'UI.ReferenceFacet',
|
|
80
|
+
ID : 'GeneralFacet',
|
|
81
|
+
Target : '@UI.FieldGroup#General',
|
|
82
|
+
Label : 'General'
|
|
83
|
+
},
|
|
84
|
+
{
|
|
85
|
+
$Type : 'UI.ReferenceFacet',
|
|
86
|
+
ID : 'ReviewsFacet',
|
|
87
|
+
Target : 'reviews/@UI.LineItem',
|
|
88
|
+
Label : 'Reviews'
|
|
89
|
+
},
|
|
90
|
+
{
|
|
91
|
+
$Type : 'UI.CollectionFacet',
|
|
92
|
+
ID : 'DetailsFacet',
|
|
93
|
+
Label : 'Details',
|
|
94
|
+
Facets : [
|
|
95
|
+
{ $Type: 'UI.ReferenceFacet', Target: '@UI.FieldGroup#Pricing' },
|
|
96
|
+
{ $Type: 'UI.ReferenceFacet', Target: '@UI.FieldGroup#Availability' }
|
|
97
|
+
]
|
|
98
|
+
}
|
|
99
|
+
];
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
## FieldGroup
|
|
103
|
+
|
|
104
|
+
```cds
|
|
105
|
+
annotate CatalogService.Books with @UI.FieldGroup#General: {
|
|
106
|
+
Data: [
|
|
107
|
+
{ Value: title },
|
|
108
|
+
{ Value: author_ID, Label: 'Author' },
|
|
109
|
+
{ Value: genre_code, Label: 'Genre' },
|
|
110
|
+
{ Value: descr, Label: 'Description' }
|
|
111
|
+
]
|
|
112
|
+
};
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
## Value Helps
|
|
116
|
+
|
|
117
|
+
```cds
|
|
118
|
+
// Simple (auto value list)
|
|
119
|
+
@cds.odata.valuelist
|
|
120
|
+
entity Genres { ... }
|
|
121
|
+
|
|
122
|
+
// Custom value help with parameters
|
|
123
|
+
annotate CatalogService.Books with {
|
|
124
|
+
author_ID @Common.ValueList: {
|
|
125
|
+
Label: 'Authors',
|
|
126
|
+
CollectionPath: 'Authors',
|
|
127
|
+
Parameters: [
|
|
128
|
+
{
|
|
129
|
+
$Type: 'Common.ValueListParameterInOut',
|
|
130
|
+
LocalDataProperty: author_ID,
|
|
131
|
+
ValueListProperty: 'ID'
|
|
132
|
+
},
|
|
133
|
+
{
|
|
134
|
+
$Type: 'Common.ValueListParameterDisplayOnly',
|
|
135
|
+
ValueListProperty: 'name'
|
|
136
|
+
}
|
|
137
|
+
]
|
|
138
|
+
};
|
|
139
|
+
};
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
## Text and TextArrangement
|
|
143
|
+
|
|
144
|
+
```cds
|
|
145
|
+
annotate CatalogService.Books with {
|
|
146
|
+
@Common.Text: author.name
|
|
147
|
+
@Common.TextArrangement: #TextOnly
|
|
148
|
+
author_ID;
|
|
149
|
+
|
|
150
|
+
@Common.Text: genre.name
|
|
151
|
+
@Common.TextArrangement: #TextFirst
|
|
152
|
+
genre_code;
|
|
153
|
+
};
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
| Value | Display | Example |
|
|
157
|
+
|---|---|---|
|
|
158
|
+
| `#TextOnly` | Text only, hide ID | "Fiction" |
|
|
159
|
+
| `#TextFirst` | Text (ID) | "Fiction (FIC)" |
|
|
160
|
+
| `#TextLast` | (ID) Text | "(FIC) Fiction" |
|
|
161
|
+
| `#TextSeparate` | Separate columns | ID and text apart |
|
|
162
|
+
|
|
163
|
+
## Field Control
|
|
164
|
+
|
|
165
|
+
```cds
|
|
166
|
+
// Static
|
|
167
|
+
annotate CatalogService.Books with {
|
|
168
|
+
ID @UI.Hidden;
|
|
169
|
+
createdAt @UI.Hidden;
|
|
170
|
+
modifiedAt @readonly;
|
|
171
|
+
};
|
|
172
|
+
|
|
173
|
+
// Dynamic field control values: 0=Hidden, 1=Mandatory, 3=Optional, 7=ReadOnly
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
## Criticality (Colors)
|
|
177
|
+
|
|
178
|
+
```cds
|
|
179
|
+
// In handler: set criticality field value
|
|
180
|
+
// 0=Neutral(grey), 1=Negative(red), 2=Critical(yellow), 3=Positive(green)
|
|
181
|
+
this.after('READ', 'Orders', orders => {
|
|
182
|
+
for (const order of orders) {
|
|
183
|
+
switch (order.status) {
|
|
184
|
+
case 'completed': order.criticality = 3; break;
|
|
185
|
+
case 'pending': order.criticality = 2; break;
|
|
186
|
+
case 'cancelled': order.criticality = 1; break;
|
|
187
|
+
default: order.criticality = 0;
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
});
|
|
191
|
+
|
|
192
|
+
// In annotation
|
|
193
|
+
annotate CatalogService.Orders with @UI.LineItem: [
|
|
194
|
+
{ Value: status, Criticality: criticality }
|
|
195
|
+
];
|
|
196
|
+
```
|
|
197
|
+
|
|
198
|
+
## Actions in UI
|
|
199
|
+
|
|
200
|
+
```cds
|
|
201
|
+
// Bound actions in CDS
|
|
202
|
+
service OrderService {
|
|
203
|
+
entity Orders { ... } actions {
|
|
204
|
+
action confirm();
|
|
205
|
+
action cancel(reason: String);
|
|
206
|
+
};
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
// In LineItem (table row action)
|
|
210
|
+
annotate OrderService.Orders with @UI.LineItem: [
|
|
211
|
+
{
|
|
212
|
+
$Type: 'UI.DataFieldForAction',
|
|
213
|
+
Action: 'OrderService.confirm',
|
|
214
|
+
Label: 'Confirm'
|
|
215
|
+
}
|
|
216
|
+
];
|
|
217
|
+
|
|
218
|
+
// In Identification (header action on object page)
|
|
219
|
+
annotate OrderService.Orders with @UI.Identification: [
|
|
220
|
+
{
|
|
221
|
+
$Type: 'UI.DataFieldForAction',
|
|
222
|
+
Action: 'OrderService.cancel',
|
|
223
|
+
Label: 'Cancel Order'
|
|
224
|
+
}
|
|
225
|
+
];
|
|
226
|
+
|
|
227
|
+
// Action availability (conditional)
|
|
228
|
+
annotate OrderService.Orders with actions {
|
|
229
|
+
confirm @Core.OperationAvailable: {
|
|
230
|
+
$edmJson: { $Eq: [{ $Path: 'status' }, 'pending'] }
|
|
231
|
+
};
|
|
232
|
+
};
|
|
233
|
+
```
|
|
234
|
+
|
|
235
|
+
## Side Effects
|
|
236
|
+
|
|
237
|
+
```cds
|
|
238
|
+
annotate AdminService.OrderItems with @Common.SideEffects: {
|
|
239
|
+
SourceProperties: [quantity],
|
|
240
|
+
TargetProperties: ['_parent/total']
|
|
241
|
+
};
|
|
242
|
+
```
|
|
243
|
+
|
|
244
|
+
## Semantic Key
|
|
245
|
+
|
|
246
|
+
```cds
|
|
247
|
+
annotate CatalogService.Books with @Common.SemanticKey: [isbn];
|
|
248
|
+
```
|
|
249
|
+
|
|
250
|
+
## Project Structure
|
|
251
|
+
|
|
252
|
+
```
|
|
253
|
+
app/
|
|
254
|
+
├── browse/
|
|
255
|
+
│ └── annotations.cds # Browse-specific annotations
|
|
256
|
+
├── admin/
|
|
257
|
+
│ └── annotations.cds # Admin-specific annotations
|
|
258
|
+
├── common.cds # Shared field labels
|
|
259
|
+
└── index.html # Test page
|
|
260
|
+
srv/
|
|
261
|
+
db/
|
|
262
|
+
```
|
|
263
|
+
|
|
264
|
+
## Separation of Concerns
|
|
265
|
+
|
|
266
|
+
```cds
|
|
267
|
+
// app/common.cds — shared labels
|
|
268
|
+
annotate CatalogService.Books with {
|
|
269
|
+
ID @title: '{i18n>ID}';
|
|
270
|
+
title @title: '{i18n>Title}';
|
|
271
|
+
};
|
|
272
|
+
|
|
273
|
+
// app/browse/annotations.cds — app-specific UI
|
|
274
|
+
annotate CatalogService.Books with @UI: {
|
|
275
|
+
SelectionFields: [title, genre_code],
|
|
276
|
+
LineItem: [
|
|
277
|
+
{ Value: title },
|
|
278
|
+
{ Value: author.name }
|
|
279
|
+
]
|
|
280
|
+
};
|
|
281
|
+
```
|
|
282
|
+
|
|
283
|
+
## Rules
|
|
284
|
+
|
|
285
|
+
- Enable `@odata.draft.enabled` on service projection, not base entity
|
|
286
|
+
- Only compositions are editable in drafts; associations are read-only
|
|
287
|
+
- Action names in annotations must be fully qualified: `ServiceName.actionName`
|
|
288
|
+
- `$Type: 'UI.DataFieldForAction'` for actions; `$Type: 'UI.DataFieldForAnnotation'` for DataPoints
|
|
289
|
+
- `ReferenceFacet` points to a FieldGroup or child LineItem; `CollectionFacet` groups facets
|
|
290
|
+
- Criticality is an integer field computed in handler, referenced by annotation
|
|
291
|
+
- Keep annotations in `app/` folder, separate from `srv/` and `db/`
|
|
292
|
+
|
|
293
|
+
## Anti-Patterns
|
|
294
|
+
|
|
295
|
+
| Anti-Pattern | Correct Pattern |
|
|
296
|
+
|---|---|
|
|
297
|
+
| `@odata.draft.enabled` on base entity in `db/` | Enable on service projection in `srv/` or `app/` |
|
|
298
|
+
| Action: `Action: 'confirm'` (unqualified) | `Action: 'OrderService.confirm'` (fully qualified) |
|
|
299
|
+
| Editing associations in draft | Only compositions are editable in drafts |
|
|
300
|
+
| Annotations mixed into `db/schema.cds` | Annotations belong in `app/` folder |
|
|
301
|
+
| Criticality as string (`'Positive'`) | Criticality is integer: 0, 1, 2, 3 |
|
|
302
|
+
| `UI.FieldGroup` without `#qualifier` when multiple exist | Use `@UI.FieldGroup#Name` for each group |
|
|
303
|
+
| ValueList without `InOut` parameter | Always include `ValueListParameterInOut` for the binding field |
|
|
@@ -0,0 +1,287 @@
|
|
|
1
|
+
# Service Definitions — CAP Service Design and Exposure
|
|
2
|
+
|
|
3
|
+
## Basic Service
|
|
4
|
+
|
|
5
|
+
```cds
|
|
6
|
+
using { my.bookshop as my } from '../db/schema';
|
|
7
|
+
|
|
8
|
+
service CatalogService @(path: '/browse') {
|
|
9
|
+
entity Books as projection on my.Books;
|
|
10
|
+
entity Authors as projection on my.Authors;
|
|
11
|
+
}
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
## Projections
|
|
15
|
+
|
|
16
|
+
```cds
|
|
17
|
+
// Column selection + exclusion
|
|
18
|
+
service CatalogService {
|
|
19
|
+
entity Books as projection on my.Books {
|
|
20
|
+
*, author.name as author
|
|
21
|
+
} excluding { createdBy, modifiedBy };
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
// With filtering
|
|
25
|
+
service CatalogService {
|
|
26
|
+
entity AvailableBooks as projection on my.Books {
|
|
27
|
+
key ID, title, price
|
|
28
|
+
} where stock > 0;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
// Calculated fields in projection
|
|
32
|
+
service CatalogService {
|
|
33
|
+
entity Books as projection on my.Books {
|
|
34
|
+
*,
|
|
35
|
+
author.name as author,
|
|
36
|
+
price * 0.9 as discountedPrice : Decimal
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
## Actions and Functions
|
|
42
|
+
|
|
43
|
+
```cds
|
|
44
|
+
service OrderService {
|
|
45
|
+
// Unbound action (service-level, changes state)
|
|
46
|
+
action cancelOrder(orderID: UUID, reason: String) returns {
|
|
47
|
+
success: Boolean;
|
|
48
|
+
message: String;
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
// Unbound function (read-only)
|
|
52
|
+
function getOrderCount() returns Integer;
|
|
53
|
+
function getBooksByGenre(genre: String) returns array of Books;
|
|
54
|
+
|
|
55
|
+
// Bound actions (entity-level)
|
|
56
|
+
entity Products {
|
|
57
|
+
key ID : UUID;
|
|
58
|
+
name : String;
|
|
59
|
+
price : Decimal;
|
|
60
|
+
stock : Integer;
|
|
61
|
+
} actions {
|
|
62
|
+
action discount(percent: Decimal) returns Decimal;
|
|
63
|
+
function getStock() returns Integer;
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
## Complex Action Types
|
|
69
|
+
|
|
70
|
+
```cds
|
|
71
|
+
service OrderService {
|
|
72
|
+
type OrderResult : {
|
|
73
|
+
success : Boolean;
|
|
74
|
+
orderID : UUID;
|
|
75
|
+
message : String;
|
|
76
|
+
items : array of {
|
|
77
|
+
productID : UUID;
|
|
78
|
+
quantity : Integer;
|
|
79
|
+
price : Decimal;
|
|
80
|
+
};
|
|
81
|
+
};
|
|
82
|
+
|
|
83
|
+
action placeOrder(order: OrderInput) returns OrderResult;
|
|
84
|
+
}
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
## Service Configuration
|
|
88
|
+
|
|
89
|
+
```cds
|
|
90
|
+
// Read-only entity
|
|
91
|
+
service CatalogService {
|
|
92
|
+
@readonly
|
|
93
|
+
entity Authors as projection on my.Authors;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// Insert-only (audit log pattern)
|
|
97
|
+
service LogService {
|
|
98
|
+
@insertonly
|
|
99
|
+
entity AuditLog as projection on my.AuditLog;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
// Draft-enabled
|
|
103
|
+
service AdminService {
|
|
104
|
+
@odata.draft.enabled
|
|
105
|
+
entity Books as projection on my.Books;
|
|
106
|
+
}
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
## Access Control
|
|
110
|
+
|
|
111
|
+
```cds
|
|
112
|
+
service MyService {
|
|
113
|
+
@requires: 'authenticated-user'
|
|
114
|
+
entity SensitiveData { ... }
|
|
115
|
+
|
|
116
|
+
@requires: ['admin', 'support']
|
|
117
|
+
action adminOperation() returns String;
|
|
118
|
+
}
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
## Protocol Configuration
|
|
122
|
+
|
|
123
|
+
```cds
|
|
124
|
+
// Default: OData V4
|
|
125
|
+
service CatalogService { ... }
|
|
126
|
+
|
|
127
|
+
// REST
|
|
128
|
+
service RESTService @(protocol: 'rest') { ... }
|
|
129
|
+
|
|
130
|
+
// Multiple protocols
|
|
131
|
+
@protocol: ['odata', 'rest']
|
|
132
|
+
service MultiService { ... }
|
|
133
|
+
|
|
134
|
+
// Internal only (no external access)
|
|
135
|
+
@protocol: 'none'
|
|
136
|
+
service InternalService { ... }
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
## Service Extension
|
|
140
|
+
|
|
141
|
+
```cds
|
|
142
|
+
extend service CatalogService with {
|
|
143
|
+
entity FeaturedBooks as projection on my.Books where featured = true;
|
|
144
|
+
}
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
## External Service Integration
|
|
148
|
+
|
|
149
|
+
```cds
|
|
150
|
+
// Define external service model
|
|
151
|
+
service ExternalAPI {
|
|
152
|
+
entity BusinessPartners { ... }
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
// Use in local service
|
|
156
|
+
using { ExternalAPI as ext } from '../external';
|
|
157
|
+
|
|
158
|
+
service MyService {
|
|
159
|
+
entity LocalPartners as projection on ext.BusinessPartners;
|
|
160
|
+
}
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
```json
|
|
164
|
+
// package.json — external service config
|
|
165
|
+
{
|
|
166
|
+
"cds": {
|
|
167
|
+
"requires": {
|
|
168
|
+
"ExternalAPI": {
|
|
169
|
+
"kind": "odata-v2",
|
|
170
|
+
"credentials": {
|
|
171
|
+
"url": "https://api.example.com"
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
## Handler Implementation
|
|
180
|
+
|
|
181
|
+
```js
|
|
182
|
+
const cds = require('@sap/cds');
|
|
183
|
+
|
|
184
|
+
module.exports = class OrderService extends cds.ApplicationService {
|
|
185
|
+
async init() {
|
|
186
|
+
const { Products, Orders } = this.entities;
|
|
187
|
+
|
|
188
|
+
this.before('CREATE', Orders, this.validateOrder);
|
|
189
|
+
this.on('cancelOrder', this.onCancelOrder);
|
|
190
|
+
this.on('discount', Products, this.onDiscount);
|
|
191
|
+
|
|
192
|
+
return super.init();
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
async validateOrder(req) {
|
|
196
|
+
if (!req.data.items?.length) req.reject(400, 'Order must have items');
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
async onCancelOrder(req) {
|
|
200
|
+
const { orderID, reason } = req.data;
|
|
201
|
+
await UPDATE('Orders', orderID).set({ status: 'cancelled', reason });
|
|
202
|
+
return { success: true, message: 'Order cancelled' };
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
async onDiscount(req) {
|
|
206
|
+
const { ID } = req.params[0];
|
|
207
|
+
const { percent } = req.data;
|
|
208
|
+
const product = await SELECT.one.from('Products', ID);
|
|
209
|
+
const newPrice = product.price * (1 - percent / 100);
|
|
210
|
+
await UPDATE('Products', ID).set({ price: newPrice });
|
|
211
|
+
return newPrice;
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
```
|
|
215
|
+
|
|
216
|
+
## Complete Multi-Service Example
|
|
217
|
+
|
|
218
|
+
```cds
|
|
219
|
+
using { sap.capire.bookshop as db } from '../db/schema';
|
|
220
|
+
|
|
221
|
+
// Public catalog (read-only)
|
|
222
|
+
service CatalogService @(path: '/browse') {
|
|
223
|
+
@readonly
|
|
224
|
+
entity Books as projection on db.Books {
|
|
225
|
+
key ID, title, descr, price, stock,
|
|
226
|
+
author { key ID, name }
|
|
227
|
+
} excluding { createdBy, modifiedBy }
|
|
228
|
+
where stock > 0;
|
|
229
|
+
|
|
230
|
+
@readonly
|
|
231
|
+
entity Authors as projection on db.Authors;
|
|
232
|
+
|
|
233
|
+
@requires: 'authenticated-user'
|
|
234
|
+
action submitOrder(book: UUID, quantity: Integer) returns {
|
|
235
|
+
orderID: UUID;
|
|
236
|
+
status: String;
|
|
237
|
+
};
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
// Admin (full CRUD + drafts)
|
|
241
|
+
service AdminService @(path: '/admin') {
|
|
242
|
+
@odata.draft.enabled
|
|
243
|
+
entity Books as projection on db.Books;
|
|
244
|
+
|
|
245
|
+
@odata.draft.enabled
|
|
246
|
+
entity Authors as projection on db.Authors;
|
|
247
|
+
|
|
248
|
+
@requires: 'admin'
|
|
249
|
+
action bulkUpdatePrices(updates: array of {
|
|
250
|
+
book: UUID;
|
|
251
|
+
newPrice: Decimal;
|
|
252
|
+
}) returns { updated: Integer; };
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
// REST API for external consumers
|
|
256
|
+
service PublicAPI @(protocol: 'rest', path: '/api/v1') {
|
|
257
|
+
entity PublicBooks as projection on db.Books {
|
|
258
|
+
key ID, title, price
|
|
259
|
+
} where published = true;
|
|
260
|
+
|
|
261
|
+
function getFeaturedBooks(limit: Integer) returns array of PublicBooks;
|
|
262
|
+
}
|
|
263
|
+
```
|
|
264
|
+
|
|
265
|
+
## Rules
|
|
266
|
+
|
|
267
|
+
- Actions change state; functions are read-only — use the correct keyword
|
|
268
|
+
- Bound actions are defined inside `entity { ... } actions { ... }` block
|
|
269
|
+
- Unbound actions are defined directly in the service body
|
|
270
|
+
- Projections should expose only what the consumer needs — minimize surface area
|
|
271
|
+
- `@readonly` prevents INSERT/UPDATE/DELETE at framework level
|
|
272
|
+
- `@protocol: 'none'` hides service from external access completely
|
|
273
|
+
- Service path defaults to service name if `@path` not specified
|
|
274
|
+
- `@requires` accepts a single role string or array of roles (any match grants access)
|
|
275
|
+
- Handler file auto-resolved: `ServiceName` looks for `srv/service-name.js`
|
|
276
|
+
|
|
277
|
+
## Anti-Patterns
|
|
278
|
+
|
|
279
|
+
| Anti-Pattern | Correct Pattern |
|
|
280
|
+
|---|---|
|
|
281
|
+
| Exposing all fields: `entity Books as projection on my.Books` without filtering | Use `excluding` or explicit column list to limit exposed fields |
|
|
282
|
+
| Using `action` for read-only operations | Use `function` for queries that don't change state |
|
|
283
|
+
| Single monolithic service for all consumers | Separate services per consumer: catalog, admin, API |
|
|
284
|
+
| `@requires` on service level blocking all access | Apply `@requires` on individual entities/actions for granularity |
|
|
285
|
+
| Defining entities directly in service (not as projections) | Project from `db/schema.cds` entities to keep single source of truth |
|
|
286
|
+
| Missing `@path` resulting in naming collisions | Always set explicit `@path` on services |
|
|
287
|
+
| External service without `cds.requires` config | Always configure credentials/URL in `package.json` |
|