@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.
Files changed (114) hide show
  1. package/README.md +384 -0
  2. package/dist/adt-client.js +364 -0
  3. package/dist/cli/activate.js +113 -0
  4. package/dist/cli/init.js +333 -0
  5. package/dist/cli/remove.js +80 -0
  6. package/dist/cli/status.js +229 -0
  7. package/dist/cli/systems.js +68 -0
  8. package/dist/cli.js +81 -0
  9. package/dist/index.js +1318 -0
  10. package/dist/knowledge/abap/abap-dictionary.md +199 -0
  11. package/dist/knowledge/abap/abap-sql.md +296 -0
  12. package/dist/knowledge/abap/amdp.md +273 -0
  13. package/dist/knowledge/abap/clean-code.md +293 -0
  14. package/dist/knowledge/abap/cloud-background-processing.md +250 -0
  15. package/dist/knowledge/abap/cloud-communication.md +265 -0
  16. package/dist/knowledge/abap/cloud-development.md +176 -0
  17. package/dist/knowledge/abap/cloud-extensibility.md +252 -0
  18. package/dist/knowledge/abap/cloud-released-apis.md +261 -0
  19. package/dist/knowledge/abap/constructor-expressions.md +289 -0
  20. package/dist/knowledge/abap/enhancements.md +232 -0
  21. package/dist/knowledge/abap/exceptions.md +271 -0
  22. package/dist/knowledge/abap/internal-tables.md +205 -0
  23. package/dist/knowledge/abap/object-orientation.md +298 -0
  24. package/dist/knowledge/abap/performance.md +216 -0
  25. package/dist/knowledge/abap/rap-abstract-entities.md +206 -0
  26. package/dist/knowledge/abap/rap-business-events.md +216 -0
  27. package/dist/knowledge/abap/rap-draft.md +191 -0
  28. package/dist/knowledge/abap/rap-eml.md +453 -0
  29. package/dist/knowledge/abap/rap-end-to-end.md +486 -0
  30. package/dist/knowledge/abap/rap-feature-control.md +185 -0
  31. package/dist/knowledge/abap/rap-numbering.md +280 -0
  32. package/dist/knowledge/abap/rap-service-exposure.md +163 -0
  33. package/dist/knowledge/abap/rap-unmanaged.md +468 -0
  34. package/dist/knowledge/abap/string-processing.md +180 -0
  35. package/dist/knowledge/abap/unit-testing.md +303 -0
  36. package/dist/knowledge/abap-cds/access-control.md +241 -0
  37. package/dist/knowledge/abap-cds/annotations.md +331 -0
  38. package/dist/knowledge/abap-cds/associations.md +254 -0
  39. package/dist/knowledge/abap-cds/expressions.md +230 -0
  40. package/dist/knowledge/abap-cds/functions.md +245 -0
  41. package/dist/knowledge/abap-cds/metadata-extensions.md +294 -0
  42. package/dist/knowledge/cap/authentication.md +278 -0
  43. package/dist/knowledge/cap/cdl-syntax.md +247 -0
  44. package/dist/knowledge/cap/cql-queries.md +266 -0
  45. package/dist/knowledge/cap/deployment.md +343 -0
  46. package/dist/knowledge/cap/event-handlers.md +287 -0
  47. package/dist/knowledge/cap/fiori-integration.md +303 -0
  48. package/dist/knowledge/cap/service-definitions.md +287 -0
  49. package/dist/knowledge/fiori/annotations.md +347 -0
  50. package/dist/knowledge/fiori/deployment.md +340 -0
  51. package/dist/knowledge/fiori/fiori-elements.md +332 -0
  52. package/dist/knowledge/fiori/fiori-side-effects.md +107 -0
  53. package/dist/knowledge/fiori/fiori-valuelist.md +144 -0
  54. package/dist/knowledge/fiori/ui5-controllers.md +358 -0
  55. package/dist/knowledge/fiori/ui5-data-binding.md +311 -0
  56. package/dist/knowledge/fiori/ui5-fragments-dialogs.md +330 -0
  57. package/dist/knowledge/fiori/ui5-manifest.md +411 -0
  58. package/dist/knowledge/fiori/ui5-routing.md +303 -0
  59. package/dist/knowledge/fiori/ui5-xml-views.md +294 -0
  60. package/dist/logger.js +114 -0
  61. package/dist/system-profile.js +207 -0
  62. package/dist/tools/abap-doc.js +72 -0
  63. package/dist/tools/abapgit.js +161 -0
  64. package/dist/tools/activate.js +68 -0
  65. package/dist/tools/atc-check.js +117 -0
  66. package/dist/tools/auth-object.js +56 -0
  67. package/dist/tools/breakpoints.js +76 -0
  68. package/dist/tools/call-hierarchy.js +84 -0
  69. package/dist/tools/cds-annotations.js +98 -0
  70. package/dist/tools/cds-dependencies.js +65 -0
  71. package/dist/tools/check.js +47 -0
  72. package/dist/tools/code-completion.js +70 -0
  73. package/dist/tools/code-coverage.js +111 -0
  74. package/dist/tools/create-amdp.js +111 -0
  75. package/dist/tools/create-dcl.js +81 -0
  76. package/dist/tools/create-transport.js +38 -0
  77. package/dist/tools/create.js +285 -0
  78. package/dist/tools/data-preview.js +37 -0
  79. package/dist/tools/delete.js +45 -0
  80. package/dist/tools/deploy-bsp.js +298 -0
  81. package/dist/tools/discovery.js +59 -0
  82. package/dist/tools/element-info.js +93 -0
  83. package/dist/tools/enhancements.js +186 -0
  84. package/dist/tools/extract-method.js +44 -0
  85. package/dist/tools/function-group.js +59 -0
  86. package/dist/tools/knowledge.js +275 -0
  87. package/dist/tools/lock-object.js +75 -0
  88. package/dist/tools/message-class.js +67 -0
  89. package/dist/tools/navigate.js +80 -0
  90. package/dist/tools/number-range.js +57 -0
  91. package/dist/tools/object-documentation.js +43 -0
  92. package/dist/tools/object-structure.js +78 -0
  93. package/dist/tools/object-versions.js +57 -0
  94. package/dist/tools/package-contents.js +60 -0
  95. package/dist/tools/pretty-printer.js +35 -0
  96. package/dist/tools/publish-binding.js +49 -0
  97. package/dist/tools/quick-fix.js +69 -0
  98. package/dist/tools/read.js +167 -0
  99. package/dist/tools/refactor-rename.js +60 -0
  100. package/dist/tools/release-transport.js +24 -0
  101. package/dist/tools/released-apis.js +51 -0
  102. package/dist/tools/repository-tree.js +90 -0
  103. package/dist/tools/scaffold-rap.js +642 -0
  104. package/dist/tools/search.js +73 -0
  105. package/dist/tools/shared/data-format.js +101 -0
  106. package/dist/tools/sql-console.js +17 -0
  107. package/dist/tools/system-info.js +270 -0
  108. package/dist/tools/traces.js +66 -0
  109. package/dist/tools/transport-contents.js +83 -0
  110. package/dist/tools/transports.js +67 -0
  111. package/dist/tools/unit-test.js +135 -0
  112. package/dist/tools/where-used.js +59 -0
  113. package/dist/tools/write.js +101 -0
  114. 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` |