@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,343 @@
|
|
|
1
|
+
# CAP Deployment — Cloud Foundry, mta.yaml, XSUAA, HDI
|
|
2
|
+
|
|
3
|
+
## mta.yaml — Complete Example
|
|
4
|
+
|
|
5
|
+
```yaml
|
|
6
|
+
_schema-version: '3.1'
|
|
7
|
+
ID: bookshop
|
|
8
|
+
description: CAP Bookshop Application
|
|
9
|
+
version: 1.0.0
|
|
10
|
+
modules:
|
|
11
|
+
|
|
12
|
+
# CAP Server (Node.js)
|
|
13
|
+
- name: bookshop-srv
|
|
14
|
+
type: nodejs
|
|
15
|
+
path: gen/srv
|
|
16
|
+
parameters:
|
|
17
|
+
buildpack: nodejs_buildpack
|
|
18
|
+
memory: 256M
|
|
19
|
+
disk-quota: 512M
|
|
20
|
+
build-parameters:
|
|
21
|
+
builder: npm
|
|
22
|
+
provides:
|
|
23
|
+
- name: srv-api
|
|
24
|
+
properties:
|
|
25
|
+
srv-url: ${default-url}
|
|
26
|
+
requires:
|
|
27
|
+
- name: bookshop-db
|
|
28
|
+
- name: bookshop-auth
|
|
29
|
+
|
|
30
|
+
# HANA DB Deployer
|
|
31
|
+
- name: bookshop-db-deployer
|
|
32
|
+
type: hdb
|
|
33
|
+
path: gen/db
|
|
34
|
+
parameters:
|
|
35
|
+
buildpack: nodejs_buildpack
|
|
36
|
+
requires:
|
|
37
|
+
- name: bookshop-db
|
|
38
|
+
|
|
39
|
+
# App Router (UI)
|
|
40
|
+
- name: bookshop-app
|
|
41
|
+
type: approuter.nodejs
|
|
42
|
+
path: app/router
|
|
43
|
+
parameters:
|
|
44
|
+
keep-existing-routes: true
|
|
45
|
+
disk-quota: 256M
|
|
46
|
+
memory: 256M
|
|
47
|
+
requires:
|
|
48
|
+
- name: srv-api
|
|
49
|
+
group: destinations
|
|
50
|
+
properties:
|
|
51
|
+
name: srv-api
|
|
52
|
+
url: ~{srv-url}
|
|
53
|
+
forwardAuthToken: true
|
|
54
|
+
- name: bookshop-auth
|
|
55
|
+
build-parameters:
|
|
56
|
+
requires:
|
|
57
|
+
- name: bookshop-ui
|
|
58
|
+
artifacts:
|
|
59
|
+
- ./*
|
|
60
|
+
target-path: resources
|
|
61
|
+
|
|
62
|
+
# UI Build
|
|
63
|
+
- name: bookshop-ui
|
|
64
|
+
type: html5
|
|
65
|
+
path: app/browse
|
|
66
|
+
build-parameters:
|
|
67
|
+
build-result: dist
|
|
68
|
+
builder: custom
|
|
69
|
+
commands:
|
|
70
|
+
- npm install
|
|
71
|
+
- npm run build
|
|
72
|
+
|
|
73
|
+
resources:
|
|
74
|
+
|
|
75
|
+
# HDI Container (HANA)
|
|
76
|
+
- name: bookshop-db
|
|
77
|
+
type: com.sap.xs.hdi-container
|
|
78
|
+
parameters:
|
|
79
|
+
service: hana
|
|
80
|
+
service-plan: hdi-shared
|
|
81
|
+
|
|
82
|
+
# XSUAA (Authentication)
|
|
83
|
+
- name: bookshop-auth
|
|
84
|
+
type: org.cloudfoundry.managed-service
|
|
85
|
+
parameters:
|
|
86
|
+
service: xsuaa
|
|
87
|
+
service-plan: application
|
|
88
|
+
path: ./xs-security.json
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
## xs-security.json — Authentication Config
|
|
92
|
+
|
|
93
|
+
```json
|
|
94
|
+
{
|
|
95
|
+
"xsappname": "bookshop",
|
|
96
|
+
"tenant-mode": "dedicated",
|
|
97
|
+
"scopes": [
|
|
98
|
+
{
|
|
99
|
+
"name": "$XSAPPNAME.admin",
|
|
100
|
+
"description": "Admin access"
|
|
101
|
+
},
|
|
102
|
+
{
|
|
103
|
+
"name": "$XSAPPNAME.viewer",
|
|
104
|
+
"description": "Read-only access"
|
|
105
|
+
}
|
|
106
|
+
],
|
|
107
|
+
"attributes": [
|
|
108
|
+
{
|
|
109
|
+
"name": "Country",
|
|
110
|
+
"description": "Country for data access",
|
|
111
|
+
"valueType": "string"
|
|
112
|
+
}
|
|
113
|
+
],
|
|
114
|
+
"role-templates": [
|
|
115
|
+
{
|
|
116
|
+
"name": "Admin",
|
|
117
|
+
"description": "Administrator",
|
|
118
|
+
"scope-references": ["$XSAPPNAME.admin"],
|
|
119
|
+
"attribute-references": ["Country"]
|
|
120
|
+
},
|
|
121
|
+
{
|
|
122
|
+
"name": "Viewer",
|
|
123
|
+
"description": "Read-only user",
|
|
124
|
+
"scope-references": ["$XSAPPNAME.viewer"]
|
|
125
|
+
}
|
|
126
|
+
],
|
|
127
|
+
"role-collections": [
|
|
128
|
+
{
|
|
129
|
+
"name": "BookshopAdmin",
|
|
130
|
+
"description": "Bookshop Administrators",
|
|
131
|
+
"role-template-references": ["$XSAPPNAME.Admin"]
|
|
132
|
+
},
|
|
133
|
+
{
|
|
134
|
+
"name": "BookshopViewer",
|
|
135
|
+
"description": "Bookshop Viewers",
|
|
136
|
+
"role-template-references": ["$XSAPPNAME.Viewer"]
|
|
137
|
+
}
|
|
138
|
+
]
|
|
139
|
+
}
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
## xs-app.json — App Router
|
|
143
|
+
|
|
144
|
+
```json
|
|
145
|
+
{
|
|
146
|
+
"welcomeFile": "/index.html",
|
|
147
|
+
"authenticationMethod": "route",
|
|
148
|
+
"routes": [
|
|
149
|
+
{
|
|
150
|
+
"source": "^/browse/(.*)$",
|
|
151
|
+
"target": "/browse/$1",
|
|
152
|
+
"destination": "srv-api",
|
|
153
|
+
"authenticationType": "xsuaa",
|
|
154
|
+
"csrfProtection": true
|
|
155
|
+
},
|
|
156
|
+
{
|
|
157
|
+
"source": "^/admin/(.*)$",
|
|
158
|
+
"target": "/admin/$1",
|
|
159
|
+
"destination": "srv-api",
|
|
160
|
+
"authenticationType": "xsuaa",
|
|
161
|
+
"csrfProtection": true
|
|
162
|
+
},
|
|
163
|
+
{
|
|
164
|
+
"source": "^(.*)$",
|
|
165
|
+
"target": "$1",
|
|
166
|
+
"service": "html5-apps-repo-rt",
|
|
167
|
+
"authenticationType": "xsuaa"
|
|
168
|
+
}
|
|
169
|
+
]
|
|
170
|
+
}
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
## CDS Service — Access Control
|
|
174
|
+
|
|
175
|
+
```cds
|
|
176
|
+
// Link CDS @requires to xs-security.json scopes
|
|
177
|
+
service AdminService @(requires: 'admin') {
|
|
178
|
+
entity Books as projection on my.Books;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
service CatalogService @(requires: 'authenticated-user') {
|
|
182
|
+
@readonly
|
|
183
|
+
entity Books as projection on my.Books;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
// Entity-level restrictions
|
|
187
|
+
service OrderService {
|
|
188
|
+
@requires: 'admin'
|
|
189
|
+
entity Orders as projection on my.Orders;
|
|
190
|
+
|
|
191
|
+
@requires: ['admin', 'support']
|
|
192
|
+
action cancelOrder(orderID: UUID);
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
// Instance-level (attribute-based)
|
|
196
|
+
annotate OrderService.Orders with @(restrict: [
|
|
197
|
+
{ grant: 'READ', to: 'viewer' },
|
|
198
|
+
{ grant: ['CREATE', 'UPDATE', 'DELETE'], to: 'admin' },
|
|
199
|
+
{ grant: 'READ', where: 'country = $user.Country' }
|
|
200
|
+
]);
|
|
201
|
+
```
|
|
202
|
+
|
|
203
|
+
## Build & Deploy Commands
|
|
204
|
+
|
|
205
|
+
```bash
|
|
206
|
+
# Install dependencies
|
|
207
|
+
npm install
|
|
208
|
+
|
|
209
|
+
# Build for production (generates gen/ folder)
|
|
210
|
+
cds build --production
|
|
211
|
+
|
|
212
|
+
# Build MTA archive
|
|
213
|
+
mbt build
|
|
214
|
+
|
|
215
|
+
# Deploy to Cloud Foundry
|
|
216
|
+
cf login -a https://api.cf.eu10.hana.ondemand.com
|
|
217
|
+
cf deploy mta_archives/bookshop_1.0.0.mtar
|
|
218
|
+
|
|
219
|
+
# Check deployment
|
|
220
|
+
cf apps
|
|
221
|
+
cf services
|
|
222
|
+
|
|
223
|
+
# View logs
|
|
224
|
+
cf logs bookshop-srv --recent
|
|
225
|
+
|
|
226
|
+
# Undeploy (removes all modules + services)
|
|
227
|
+
cf undeploy bookshop --delete-services
|
|
228
|
+
```
|
|
229
|
+
|
|
230
|
+
## HANA Configuration
|
|
231
|
+
|
|
232
|
+
```json
|
|
233
|
+
// package.json — database config
|
|
234
|
+
{
|
|
235
|
+
"cds": {
|
|
236
|
+
"requires": {
|
|
237
|
+
"db": {
|
|
238
|
+
"kind": "hana",
|
|
239
|
+
"[development]": {
|
|
240
|
+
"kind": "sqlite",
|
|
241
|
+
"credentials": { "database": ":memory:" }
|
|
242
|
+
},
|
|
243
|
+
"[production]": {
|
|
244
|
+
"kind": "hana"
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
},
|
|
248
|
+
"hana": {
|
|
249
|
+
"deploy-format": "hdbtable"
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
```
|
|
254
|
+
|
|
255
|
+
## Hybrid Development (Local + HANA)
|
|
256
|
+
|
|
257
|
+
```bash
|
|
258
|
+
# Bind to remote HANA service
|
|
259
|
+
cf create-service-key bookshop-db bookshop-db-key
|
|
260
|
+
cds bind --to bookshop-db:bookshop-db-key
|
|
261
|
+
|
|
262
|
+
# Run locally with remote HANA
|
|
263
|
+
cds watch --profile hybrid
|
|
264
|
+
```
|
|
265
|
+
|
|
266
|
+
## .cdsrc.json — Project Config
|
|
267
|
+
|
|
268
|
+
```json
|
|
269
|
+
{
|
|
270
|
+
"build": {
|
|
271
|
+
"target": "gen",
|
|
272
|
+
"tasks": [
|
|
273
|
+
{ "for": "hana", "src": "db", "options": { "model": ["db", "srv"] } },
|
|
274
|
+
{ "for": "node-cf", "src": "srv", "options": { "model": ["db", "srv"] } }
|
|
275
|
+
]
|
|
276
|
+
},
|
|
277
|
+
"odata": {
|
|
278
|
+
"version": "v4"
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
```
|
|
282
|
+
|
|
283
|
+
## Project Structure (Production)
|
|
284
|
+
|
|
285
|
+
```
|
|
286
|
+
project/
|
|
287
|
+
├── app/
|
|
288
|
+
│ ├── browse/ # UI module (Fiori)
|
|
289
|
+
│ │ ├── webapp/
|
|
290
|
+
│ │ └── package.json
|
|
291
|
+
│ ├── router/ # App Router
|
|
292
|
+
│ │ ├── xs-app.json
|
|
293
|
+
│ │ └── package.json
|
|
294
|
+
│ └── common.cds # Shared annotations
|
|
295
|
+
├── db/
|
|
296
|
+
│ ├── schema.cds # Data model
|
|
297
|
+
│ ├── data/ # CSV seed data
|
|
298
|
+
│ │ └── my.bookshop-Books.csv
|
|
299
|
+
│ └── undeploy.json
|
|
300
|
+
├── srv/
|
|
301
|
+
│ ├── catalog-service.cds
|
|
302
|
+
│ ├── catalog-service.js
|
|
303
|
+
│ ├── admin-service.cds
|
|
304
|
+
│ └── admin-service.js
|
|
305
|
+
├── mta.yaml
|
|
306
|
+
├── xs-security.json
|
|
307
|
+
├── package.json
|
|
308
|
+
└── .cdsrc.json
|
|
309
|
+
```
|
|
310
|
+
|
|
311
|
+
## CSV Seed Data
|
|
312
|
+
|
|
313
|
+
```
|
|
314
|
+
# db/data/my.bookshop-Books.csv
|
|
315
|
+
ID;title;author_ID;stock;price;currency_code
|
|
316
|
+
1;Wuthering Heights;101;12;11.11;EUR
|
|
317
|
+
2;Jane Eyre;107;11;12.34;EUR
|
|
318
|
+
3;The Raven;150;333;13.13;USD
|
|
319
|
+
```
|
|
320
|
+
|
|
321
|
+
File naming: `<namespace>-<Entity>.csv` with semicolon separator.
|
|
322
|
+
|
|
323
|
+
## Rules
|
|
324
|
+
- `cds build --production` before `mbt build` (generates gen/ folder)
|
|
325
|
+
- xs-security.json `xsappname` must match MTA ID
|
|
326
|
+
- `$XSAPPNAME` prefix auto-resolves to the app name at deploy time
|
|
327
|
+
- HDI container: `hdi-shared` plan for shared HANA, `hdi-dynamic` for multi-tenant
|
|
328
|
+
- App Router routes: order matters — first match wins
|
|
329
|
+
- `@requires: 'authenticated-user'` for any logged-in user (no specific role)
|
|
330
|
+
- CSV seed data: deployed with `hdb` deployer, semicolon separated
|
|
331
|
+
- `[development]` / `[production]` profiles in package.json for env-specific config
|
|
332
|
+
|
|
333
|
+
## Anti-Patterns
|
|
334
|
+
| Anti-Pattern | Correct |
|
|
335
|
+
|---|---|
|
|
336
|
+
| Deploying without `cds build --production` | Build generates optimized gen/ folder |
|
|
337
|
+
| Missing xs-security.json in mta.yaml | `path: ./xs-security.json` in auth resource |
|
|
338
|
+
| Hardcoded credentials in code | Use XSUAA + destination service |
|
|
339
|
+
| `tenant-mode: shared` for single-tenant | Use `dedicated` unless multi-tenant SaaS |
|
|
340
|
+
| Missing CSRF protection on routes | `csrfProtection: true` in xs-app.json |
|
|
341
|
+
| App Router without `forwardAuthToken` | Required for auth propagation to srv |
|
|
342
|
+
| Deploying test data to production | Use `[development]` profile for seed data |
|
|
343
|
+
| `cf push` instead of `cf deploy` | Use MTA deployment for multi-module apps |
|
|
@@ -0,0 +1,287 @@
|
|
|
1
|
+
# Event Handlers — CAP Node.js Service Implementation
|
|
2
|
+
|
|
3
|
+
## Handler Registration (Class-Based)
|
|
4
|
+
|
|
5
|
+
```js
|
|
6
|
+
const cds = require('@sap/cds');
|
|
7
|
+
|
|
8
|
+
module.exports = class CatalogService extends cds.ApplicationService {
|
|
9
|
+
async init() {
|
|
10
|
+
const { Books, Authors } = this.entities;
|
|
11
|
+
|
|
12
|
+
this.before('CREATE', Books, this.validateBook);
|
|
13
|
+
this.on('READ', Books, this.onReadBooks);
|
|
14
|
+
this.after('READ', Books, this.enrichBooks);
|
|
15
|
+
this.on('submitOrder', this.onSubmitOrder);
|
|
16
|
+
|
|
17
|
+
return super.init();
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
validateBook(req) { /* ... */ }
|
|
21
|
+
async onReadBooks(req) { /* ... */ }
|
|
22
|
+
enrichBooks(books, req) { /* ... */ }
|
|
23
|
+
async onSubmitOrder(req) { /* ... */ }
|
|
24
|
+
}
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
## Handler Registration (Functional)
|
|
28
|
+
|
|
29
|
+
```js
|
|
30
|
+
module.exports = function() {
|
|
31
|
+
const { Books } = this.entities;
|
|
32
|
+
|
|
33
|
+
this.before('CREATE', Books, validateBook);
|
|
34
|
+
this.on('READ', Books, onReadBooks);
|
|
35
|
+
this.after('READ', Books, enrichBooks);
|
|
36
|
+
|
|
37
|
+
function validateBook(req) { /* ... */ }
|
|
38
|
+
async function onReadBooks(req) { /* ... */ }
|
|
39
|
+
function enrichBooks(books, req) { /* ... */ }
|
|
40
|
+
}
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
## before Handlers — Validation and Enrichment
|
|
44
|
+
|
|
45
|
+
```js
|
|
46
|
+
// Validation (collect multiple errors)
|
|
47
|
+
this.before('CREATE', 'Books', (req) => {
|
|
48
|
+
const { title, price } = req.data;
|
|
49
|
+
if (!title) req.error(400, 'Title is required', 'title');
|
|
50
|
+
if (price < 0) req.error(400, 'Price must be positive', 'price');
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
// Enrichment
|
|
54
|
+
this.before('CREATE', 'Orders', (req) => {
|
|
55
|
+
req.data.status = 'draft';
|
|
56
|
+
req.data.createdAt = new Date();
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
// Authorization
|
|
60
|
+
this.before('UPDATE', 'Books', async (req) => {
|
|
61
|
+
const book = await SELECT.one.from('Books', req.data.ID);
|
|
62
|
+
if (book.createdBy !== req.user.id && !req.user.is('admin')) {
|
|
63
|
+
req.reject(403, 'Not authorized');
|
|
64
|
+
}
|
|
65
|
+
});
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
## on Handlers — Custom Implementation
|
|
69
|
+
|
|
70
|
+
```js
|
|
71
|
+
// Custom READ
|
|
72
|
+
this.on('READ', 'Books', async (req) => {
|
|
73
|
+
return await cds.db.run(req.query);
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
// Delegate to default + post-process
|
|
77
|
+
this.on('READ', 'Books', async (req, next) => {
|
|
78
|
+
const result = await next();
|
|
79
|
+
return result.filter(b => b.stock > 0);
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
// Action handler
|
|
83
|
+
this.on('submitOrder', async (req) => {
|
|
84
|
+
const { book, quantity } = req.data;
|
|
85
|
+
const b = await SELECT.one.from('Books', book);
|
|
86
|
+
if (!b) return req.reject(404, 'Book not found');
|
|
87
|
+
if (b.stock < quantity) return req.reject(409, 'Insufficient stock');
|
|
88
|
+
await UPDATE('Books', book).set({ stock: { '-=': quantity } });
|
|
89
|
+
return { success: true };
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
// Bound action
|
|
93
|
+
this.on('confirm', 'Orders', async (req) => {
|
|
94
|
+
const { ID } = req.params[0];
|
|
95
|
+
await UPDATE('Orders', ID).set({ status: 'confirmed' });
|
|
96
|
+
});
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
## after Handlers — Post-Processing
|
|
100
|
+
|
|
101
|
+
```js
|
|
102
|
+
// Enrich results (first param is result data)
|
|
103
|
+
this.after('READ', 'Books', (books, req) => {
|
|
104
|
+
for (const book of books) {
|
|
105
|
+
book.discount = book.stock > 100 ? '10%' : null;
|
|
106
|
+
book.available = book.stock > 0;
|
|
107
|
+
}
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
// Emit event after creation
|
|
111
|
+
this.after('CREATE', 'Orders', async (order, req) => {
|
|
112
|
+
await this.emit('OrderCreated', { orderID: order.ID });
|
|
113
|
+
});
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
## Request Object (req)
|
|
117
|
+
|
|
118
|
+
```js
|
|
119
|
+
this.on('CREATE', 'Books', (req) => {
|
|
120
|
+
req.event; // 'CREATE', 'READ', 'UPDATE', 'DELETE', or action name
|
|
121
|
+
req.target; // Entity definition (CSN)
|
|
122
|
+
req.entity; // Entity name string
|
|
123
|
+
req.data; // Request payload
|
|
124
|
+
req.params; // URL parameters [{ID: '123'}]
|
|
125
|
+
req.query; // CQN query object
|
|
126
|
+
req.user.id; // User ID
|
|
127
|
+
req.user.is('admin'); // Role check
|
|
128
|
+
req.user.attr.country; // User attribute
|
|
129
|
+
req.tenant; // Tenant ID
|
|
130
|
+
req.locale; // User locale
|
|
131
|
+
req.timestamp; // Request timestamp
|
|
132
|
+
});
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
## Error Handling
|
|
136
|
+
|
|
137
|
+
```js
|
|
138
|
+
// Immediate rejection (stops processing)
|
|
139
|
+
req.reject(400, 'Bad request');
|
|
140
|
+
req.reject(404, 'Not found');
|
|
141
|
+
|
|
142
|
+
// Collect errors (continues, rejects at end of phase)
|
|
143
|
+
req.error(400, 'Title required', 'title');
|
|
144
|
+
req.error(400, 'Price required', 'price');
|
|
145
|
+
|
|
146
|
+
// Warnings and info (returned in response headers, no rejection)
|
|
147
|
+
req.warn(200, 'Stock is low');
|
|
148
|
+
req.info(200, 'Price updated');
|
|
149
|
+
|
|
150
|
+
// Standard CAP error classes
|
|
151
|
+
const { errors } = require('@sap/cds');
|
|
152
|
+
throw new errors.NotFound('Book not found');
|
|
153
|
+
throw new errors.Unauthorized('Login required');
|
|
154
|
+
throw new errors.Forbidden('Access denied');
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
## Wildcard Handlers
|
|
158
|
+
|
|
159
|
+
```js
|
|
160
|
+
this.before('*', 'Books', handler); // All events on Books
|
|
161
|
+
this.before('CREATE', '*', handler); // CREATE on all entities
|
|
162
|
+
this.before('*', handler); // All events on all entities
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
## Draft Events
|
|
166
|
+
|
|
167
|
+
```js
|
|
168
|
+
this.on('NEW', 'Books', handler); // Create draft
|
|
169
|
+
this.on('EDIT', 'Books', handler); // Edit existing as draft
|
|
170
|
+
this.on('PATCH', 'Books', handler); // Update draft fields
|
|
171
|
+
this.on('SAVE', 'Books', handler); // Activate/save draft
|
|
172
|
+
this.on('CANCEL', 'Books', handler); // Discard draft
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
## Database Operations in Handlers
|
|
176
|
+
|
|
177
|
+
```js
|
|
178
|
+
const { Books } = cds.entities;
|
|
179
|
+
|
|
180
|
+
const books = await SELECT.from(Books).where({ stock: { '>': 0 } });
|
|
181
|
+
const book = await SELECT.one.from(Books, bookId);
|
|
182
|
+
await INSERT.into(Books).entries({ title: 'New', stock: 10 });
|
|
183
|
+
await UPDATE(Books, bookId).set({ stock: 50 });
|
|
184
|
+
await DELETE.from(Books, bookId);
|
|
185
|
+
await UPSERT.into(Books).entries({ ID: bookId, title: 'Updated' });
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
## Transactions
|
|
189
|
+
|
|
190
|
+
```js
|
|
191
|
+
this.on('transferStock', async (req) => {
|
|
192
|
+
const { from, to, amount } = req.data;
|
|
193
|
+
|
|
194
|
+
// Handlers run inside automatic transaction
|
|
195
|
+
await UPDATE(Books, from).set({ stock: { '-=': amount } });
|
|
196
|
+
await UPDATE(Books, to).set({ stock: { '+=': amount } });
|
|
197
|
+
|
|
198
|
+
// Explicit transaction (when needed)
|
|
199
|
+
return cds.tx(async (tx) => {
|
|
200
|
+
await tx.run(UPDATE(Books, from).set({ stock: { '-=': amount } }));
|
|
201
|
+
await tx.run(UPDATE(Books, to).set({ stock: { '+=': amount } }));
|
|
202
|
+
return { success: true };
|
|
203
|
+
});
|
|
204
|
+
});
|
|
205
|
+
```
|
|
206
|
+
|
|
207
|
+
## Service-to-Service Communication
|
|
208
|
+
|
|
209
|
+
```js
|
|
210
|
+
const adminSrv = await cds.connect.to('AdminService');
|
|
211
|
+
const books = await adminSrv.read('Books');
|
|
212
|
+
await adminSrv.send('updateStock', { book: bookId, delta: 10 });
|
|
213
|
+
|
|
214
|
+
const externalApi = await cds.connect.to('ExternalAPI');
|
|
215
|
+
const result = await externalApi.run(SELECT.from('Products'));
|
|
216
|
+
```
|
|
217
|
+
|
|
218
|
+
## Event Emission and Subscription
|
|
219
|
+
|
|
220
|
+
```js
|
|
221
|
+
// Emit
|
|
222
|
+
this.emit('OrderCreated', { orderID: order.ID });
|
|
223
|
+
|
|
224
|
+
// Subscribe
|
|
225
|
+
const messaging = await cds.connect.to('messaging');
|
|
226
|
+
messaging.on('OrderCreated', async (msg) => {
|
|
227
|
+
const { orderID } = msg.data;
|
|
228
|
+
});
|
|
229
|
+
```
|
|
230
|
+
|
|
231
|
+
## Lifecycle Hooks
|
|
232
|
+
|
|
233
|
+
```js
|
|
234
|
+
req.before('commit', async () => { /* before transaction commit */ });
|
|
235
|
+
req.on('succeeded', async () => { /* after successful commit (outside tx) */ });
|
|
236
|
+
req.on('failed', async () => { /* after rollback */ });
|
|
237
|
+
req.on('done', async () => { /* always — cleanup */ });
|
|
238
|
+
```
|
|
239
|
+
|
|
240
|
+
## Context Access
|
|
241
|
+
|
|
242
|
+
```js
|
|
243
|
+
// Available anywhere in async call chain
|
|
244
|
+
const { user, tenant, locale } = cds.context;
|
|
245
|
+
|
|
246
|
+
// Set context for background jobs
|
|
247
|
+
cds.context = { user: new cds.User('system'), tenant: 't1' };
|
|
248
|
+
```
|
|
249
|
+
|
|
250
|
+
## TypeScript
|
|
251
|
+
|
|
252
|
+
```ts
|
|
253
|
+
import cds from '@sap/cds';
|
|
254
|
+
import { Request } from '@sap/cds';
|
|
255
|
+
|
|
256
|
+
export default class CatalogService extends cds.ApplicationService {
|
|
257
|
+
async init() {
|
|
258
|
+
this.on('READ', 'Books', this.readBooks);
|
|
259
|
+
return super.init();
|
|
260
|
+
}
|
|
261
|
+
private async readBooks(req: Request) {
|
|
262
|
+
return await cds.db.run(req.query);
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
```
|
|
266
|
+
|
|
267
|
+
## Rules
|
|
268
|
+
|
|
269
|
+
- `before` handlers: validation and enrichment; `on` handlers: replace default logic; `after` handlers: post-process results
|
|
270
|
+
- `req.reject()` stops immediately; `req.error()` collects and rejects at end of phase
|
|
271
|
+
- `after('READ')` callback signature is `(results, req)` — results first
|
|
272
|
+
- Handlers run inside automatic transactions; explicit `cds.tx()` rarely needed
|
|
273
|
+
- Always call `return super.init()` at end of class-based `init()`
|
|
274
|
+
- Use `next()` in `on` handlers to delegate to the default/next handler in chain
|
|
275
|
+
- Bound action params accessed via `req.params[0]` for the entity key, `req.data` for action params
|
|
276
|
+
|
|
277
|
+
## Anti-Patterns
|
|
278
|
+
|
|
279
|
+
| Anti-Pattern | Correct Pattern |
|
|
280
|
+
|---|---|
|
|
281
|
+
| Forgetting `return super.init()` in class-based handler | Always end `init()` with `return super.init()` |
|
|
282
|
+
| Using `req.reject()` for multiple validation errors | Use `req.error()` to collect, framework rejects if errors exist |
|
|
283
|
+
| `after('READ')` with `(req, books)` param order | Correct order: `(books, req)` |
|
|
284
|
+
| Business logic in `before` that should be in `on` | `before` = validate/enrich; `on` = business logic |
|
|
285
|
+
| Direct DB writes without considering CAP transactions | Use CQL APIs; handlers auto-manage transactions |
|
|
286
|
+
| Forgetting `async` on handlers that use `await` | Always mark handlers as `async` when using `await` |
|
|
287
|
+
| Not calling `next()` when extending default READ | Without `next()`, default query is skipped entirely |
|