Compare commits

..

2 Commits

Author SHA1 Message Date
Daniel Hutzel
56ea989c19 Merge branch 'main' into new-capire 2024-12-18 15:57:32 +01:00
Daniel Hutzel
22709b8cdb Improved samples for new capire 2024-11-19 12:29:19 +01:00
18 changed files with 147 additions and 258 deletions

View File

@@ -19,13 +19,13 @@ const books = Vue.createApp ({
search: ({target:{value:v}}) => books.fetch(v && '&$search='+v), search: ({target:{value:v}}) => books.fetch(v && '&$search='+v),
async fetch (etc='') { async fetch (etc='') {
const {data} = await GET(`/ListOfBooks?$expand=genre($select=name),currency($select=symbol)${etc}`) const {data} = await GET(`/Books?${etc}`)
books.list = data.value books.list = data.value
}, },
async inspect (eve) { async inspect (eve) {
const book = books.book = books.list [eve.currentTarget.rowIndex-1] const book = books.book = books.list [eve.currentTarget.rowIndex-1]
const res = await GET(`/Books/${book.ID}?$select=descr,stock,image`) const res = await GET(`/Book/${book.ID}?$select=descr,stock,image`)
Object.assign (book, res.data) Object.assign (book, res.data)
books.order = { quantity:1 } books.order = { quantity:1 }
setTimeout (()=> $('form > input').focus(), 111) setTimeout (()=> $('form > input').focus(), 111)

View File

@@ -45,11 +45,11 @@
<tr v-for="book in list" v-bind:id="book.ID" v-on:click="inspect"> <tr v-for="book in list" v-bind:id="book.ID" v-on:click="inspect">
<td>{{ book.title }}</td> <td>{{ book.title }}</td>
<td>{{ book.author }}</td> <td>{{ book.author }}</td>
<td>{{ book.genre.name }}</td> <td>{{ book.genre }}</td>
<td class="rating-stars"> <td class="rating-stars">
{{ ('★'.repeat(Math.round(book.rating))+'☆☆☆☆☆').slice(0,5) }} ({{ book.numberOfReviews }}) {{ ('★'.repeat(Math.round(book.rating))+'☆☆☆☆☆').slice(0,5) }} ({{ book.numberOfReviews }})
</td> </td>
<td>{{ book.currency && book.currency.symbol }} {{ book.price }}</td> <td>{{ book.currency }} {{ book.price }}</td>
</tr> </tr>
</table> </table>

View File

@@ -13,7 +13,6 @@
"@cap-js/sqlite": "*" "@cap-js/sqlite": "*"
}, },
"dependencies": { "dependencies": {
"@cap-js/ord": "2",
"@sap/cds": ">=7", "@sap/cds": ">=7",
"express": "^4.17.1" "express": "^4.17.1"
}, },

View File

@@ -1,5 +1,5 @@
using { sap.capire.bookshop as my } from '../db/schema'; using { sap.capire.bookshop as my } from '../db/schema';
service AdminService @(requires:'admin', path:'/admin') { service AdminService @(requires:'admin', path:'/admin') {
entity Books as projection on my.Books; entity Books as projection on my.Books;
entity Authors as projection on my.Authors; // entity Authors as projection on my.Authors;
} }

View File

@@ -2,15 +2,20 @@ using { sap.capire.bookshop as my } from '../db/schema';
service CatalogService @(path:'/browse') { service CatalogService @(path:'/browse') {
/** For displaying lists of Books */ /** For displaying lists of Books */
@readonly entity ListOfBooks as projection on Books @readonly entity Books as projection on Book excluding { descr };
excluding { descr };
/** For display in details pages */ /** For display in details pages */
@readonly entity Books as projection on my.Books { *, @readonly entity Book as projection on my.Books { *,
author.name as author currency.name as currencyName, // flattened
} excluding { createdBy, modifiedBy }; currency.symbol as currency, // flattened
author.name as author, // flattened
genre.name as genre, // flattened
} excluding {
createdBy, modifiedBy, // as end users shouldn't see them
localized, texts, // as end users don't need them
};
@requires: 'authenticated-user' @requires: 'authenticated-user'
action submitOrder ( book: Books:ID, quantity: Integer ) returns { stock: Integer }; action submitOrder ( book: Book:ID, quantity: Integer ) returns { stock: Integer };
event OrderedBook : { book: Books:ID; quantity: Integer; buyer: String }; event OrderedBook : { book: Book:ID; quantity: Integer; buyer: String };
} }

View File

@@ -3,10 +3,10 @@ const cds = require('@sap/cds')
class CatalogService extends cds.ApplicationService { init() { class CatalogService extends cds.ApplicationService { init() {
const { Books } = cds.entities('sap.capire.bookshop') const { Books } = cds.entities('sap.capire.bookshop')
const { ListOfBooks } = this.entities const { Books:Book } = this.entities
// Add some discount for overstocked books // Add some discount for overstocked books
this.after('each', ListOfBooks, book => { this.after('each', Book, book => {
if (book.stock > 111) book.title += ` -- 11% discount!` if (book.stock > 111) book.title += ` -- 11% discount!`
}) })

View File

@@ -1,5 +1,6 @@
@server = http://localhost:4004 @server = http://localhost:4004
@me = Authorization: Basic {{$processEnv USER}}: @me = Authorization: Basic {{$processEnv USER}}:
@alice = Authorization: Basic alice:
### ------------------------------------------------------------------------ ### ------------------------------------------------------------------------
@@ -16,11 +17,11 @@ GET {{server}}/browse/$metadata
### ------------------------------------------------------------------------ ### ------------------------------------------------------------------------
# Browse Books as any user # Browse Books as any user
GET {{server}}/browse/ListOfBooks? GET {{server}}/admin/Books?
# &$select=title,stock # &$select=title,stock
&$expand=genre &$expand=genre
# &sap-language=de # &sap-language=de
{{me}} {{alice}}
### ------------------------------------------------------------------------ ### ------------------------------------------------------------------------
@@ -30,13 +31,13 @@ GET {{server}}/admin/Authors?
# &$expand=books($select=title;$expand=currency) # &$expand=books($select=title;$expand=currency)
# &$filter=ID eq 101 # &$filter=ID eq 101
# &sap-language=de # &sap-language=de
Authorization: Basic alice: {{alice}}
### ------------------------------------------------------------------------ ### ------------------------------------------------------------------------
# Create Author # Create Author
POST {{server}}/admin/Authors POST {{server}}/admin/Authors
Content-Type: application/json;IEEE754Compatible=true Content-Type: application/json;IEEE754Compatible=true
Authorization: Basic alice: {{alice}}
{ {
"ID": 112, "ID": 112,
@@ -49,7 +50,7 @@ Authorization: Basic alice:
# Create book # Create book
POST {{server}}/admin/Books POST {{server}}/admin/Books
Content-Type: application/json;IEEE754Compatible=true Content-Type: application/json;IEEE754Compatible=true
Authorization: Basic alice: {{alice}}
{ {
"ID": 2, "ID": 2,
@@ -67,7 +68,7 @@ Authorization: Basic alice:
# Put image to books # Put image to books
PUT {{server}}/admin/Books(2)/image PUT {{server}}/admin/Books(2)/image
Content-Type: image/png Content-Type: image/png
Authorization: Basic alice: {{alice}}
data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAANwAAADcCAYAAAAbWs+BAAAGwElEQVR4Ae3cwZFbNxBFUY5rkrDTmKAUk5QT03Aa44U22KC7NHptw+DRikVAXf8fzC3u8Hj4R4AAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAgZzAW26USQT+e4HPx+Mz+RRvj0e0kT+SD2cWAQK1gOBqH6sEogKCi3IaRqAWEFztY5VAVEBwUU7DCNQCgqt9rBKICgguymkYgVpAcLWPVQJRAcFFOQ0jUAsIrvaxSiAqILgop2EEagHB1T5WCUQFBBflNIxALSC42scqgaiA4KKchhGoBQRX+1glEBUQXJTTMAK1gOBqH6sEogKCi3IaRqAWeK+Xb1z9iN558fHxcSPS9p2ezx/ROz4e4TtIHt+3j/61hW9f+2+7/+UXbifjewIDAoIbQDWSwE5AcDsZ3xMYEBDcAKqRBHYCgtvJ+J7AgIDgBlCNJLATENxOxvcEBgQEN4BqJIGdgOB2Mr4nMCAguAFUIwnsBAS3k/E9gQEBwQ2gGklgJyC4nYzvCQwICG4A1UgCOwHB7WR8T2BAQHADqEYS2AkIbifjewIDAoIbQDWSwE5AcDsZ3xMYEEjfTzHwiK91B8npd6Q8n8/oGQ/ckRJ9vvQwv3BpUfMIFAKCK3AsEUgLCC4tah6BQkBwBY4lAmkBwaVFzSNQCAiuwLFEIC0guLSoeQQKAcEVOJYIpAUElxY1j0AhILgCxxKBtIDg0qLmESgEBFfgWCKQFhBcWtQ8AoWA4AocSwTSAoJLi5pHoBAQXIFjiUBaQHBpUfMIFAKCK3AsEUgLCC4tah6BQmDgTpPsHSTFs39p6fQ7Q770UsV/Ov19X+2OFL9wxR+rJQJpAcGlRc0jUAgIrsCxRCAtILi0qHkECgHBFTiWCKQFBJcWNY9AISC4AscSgbSA4NKi5hEoBARX4FgikBYQXFrUPAKFgOAKHEsE0gKCS4uaR6AQEFyBY4lAWkBwaVHzCBQCgitwLBFICwguLWoegUJAcAWOJQJpAcGlRc0jUAgIrsCxRCAt8J4eePq89B0ar3ZnyOnve/rfn1+400/I810lILirjtPLnC4guNNPyPNdJSC4q47Ty5wuILjTT8jzXSUguKuO08ucLiC400/I810lILirjtPLnC4guNNPyPNdJSC4q47Ty5wuILjTT8jzXSUguKuO08ucLiC400/I810lILirjtPLnC4guNNPyPNdJSC4q47Ty5wuILjTT8jzXSUguKuO08ucLiC400/I810l8JZ/m78+szP/zI47fJo7Q37vgJ7PHwN/07/3TOv/9gu3avhMYFhAcMPAxhNYBQS3avhMYFhAcMPAxhNYBQS3avhMYFhAcMPAxhNYBQS3avhMYFhAcMPAxhNYBQS3avhMYFhAcMPAxhNYBQS3avhMYFhAcMPAxhNYBQS3avhMYFhAcMPAxhNYBQS3avhMYFhAcMPAxhNYBQS3avhMYFhAcMPAxhNYBQS3avhMYFhg4P6H9J0maYHXuiMlrXf+vOfA33Turf3C5SxNItAKCK4lsoFATkBwOUuTCLQCgmuJbCCQExBcztIkAq2A4FoiGwjkBASXszSJQCsguJbIBgI5AcHlLE0i0AoIriWygUBOQHA5S5MItAKCa4lsIJATEFzO0iQCrYDgWiIbCOQEBJezNIlAKyC4lsgGAjkBweUsTSLQCgiuJbKBQE5AcDlLkwi0Akff//Dz6U+/I6U1/sUNr3bnytl3kPzi4bXb/cK1RDYQyAkILmdpEoFWQHAtkQ0EcgKCy1maRKAVEFxLZAOBnIDgcpYmEWgFBNcS2UAgJyC4nKVJBFoBwbVENhDICQguZ2kSgVZAcC2RDQRyAoLLWZpEoBUQXEtkA4GcgOByliYRaAUE1xLZQCAnILicpUkEWgHBtUQ2EMgJCC5naRKBVkBwLZENBHIC/4M7TXIv+3PS22d24qvdQfL3C/7N5P5i/MLlLE0i0AoIriWygUBOQHA5S5MItAKCa4lsIJATEFzO0iQCrYDgWiIbCOQEBJezNIlAKyC4lsgGAjkBweUsTSLQCgiuJbKBQE5AcDlLkwi0AoJriWwgkBMQXM7SJAKtgOBaIhsI5AQEl7M0iUArILiWyAYCOQHB5SxNItAKCK4lsoFATkBwOUuTCBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIDAvyrwDySEJ2VQgUSoAAAAAElFTkSuQmCC data:image/png;base64,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

View File

@@ -28,8 +28,8 @@ module.exports = async()=>{ // called by server.js
// //
CatalogService.on ('OrderedBook', async (msg) => { CatalogService.on ('OrderedBook', async (msg) => {
const { book, quantity, buyer } = msg.data const { book, quantity, buyer } = msg.data
const { title, price } = await db.read (Books, book, b => { b.title, b.price }) const { title, price } = await db.tx(msg).read (Books, book, b => { b.title, b.price })
return OrdersService.create ('Orders').entries({ return OrdersService.tx(msg).create ('Orders').entries({
OrderNo: 'Order at '+ (new Date).toLocaleString(), OrderNo: 'Order at '+ (new Date).toLocaleString(),
Items: [{ product:{ID:`${book}`}, title, price, quantity }], Items: [{ product:{ID:`${book}`}, title, price, quantity }],
buyer, createdBy: buyer buyer, createdBy: buyer

View File

@@ -4,7 +4,7 @@ using CatalogService from '@capire/bookstore';
// //
// Books Object Page // Books Object Page
// //
annotate CatalogService.Books with @(UI : { annotate CatalogService.Book with @(UI : {
HeaderInfo : { HeaderInfo : {
TypeName : '{i18n>Book}', TypeName : '{i18n>Book}',
TypeNamePlural : '{i18n>Books}', TypeNamePlural : '{i18n>Books}',
@@ -24,7 +24,7 @@ annotate CatalogService.Books with @(UI : {
FieldGroup #Price : {Data : [ FieldGroup #Price : {Data : [
{Value : price}, {Value : price},
{ {
Value : currency.symbol, Value : currencyName,
Label : '{i18n>Currency}' Label : '{i18n>Currency}'
}, },
]}, ]},
@@ -35,11 +35,11 @@ annotate CatalogService.Books with @(UI : {
// //
// Books List Page // Books List Page
// //
annotate CatalogService.Books with @(UI : { annotate CatalogService.Book with @(UI : {
SelectionFields : [ SelectionFields : [
ID, ID,
price, price,
currency_code currencyName
], ],
LineItem : [ LineItem : [
{ {
@@ -50,8 +50,10 @@ annotate CatalogService.Books with @(UI : {
Value : author, Value : author,
Label : '{i18n>Author}' Label : '{i18n>Author}'
}, },
{Value : genre.name}, {Value : genre},
{Value : price}, {Value : price},
{Value : currency.symbol}, {Value : currencyName},
] ]
}, ); }) {
currencyName @Common.Label : '{i18n>Currency}';
};

View File

@@ -18,7 +18,6 @@ annotate my.Books with @(
ID, ID,
author_ID, author_ID,
price, price,
currency_code
], ],
LineItem : [ LineItem : [
{ Value: ID, Label: '{i18n>Title}' }, { Value: ID, Label: '{i18n>Title}' },
@@ -26,7 +25,6 @@ annotate my.Books with @(
{ Value: genre.name }, { Value: genre.name },
{ Value: stock }, { Value: stock },
{ Value: price }, { Value: price },
{ Value: currency.symbol },
] ]
} }
) { ) {
@@ -63,7 +61,7 @@ annotate my.Books with {
title @title: '{i18n>Title}'; title @title: '{i18n>Title}';
genre @title: '{i18n>Genre}' @Common: { Text: genre.name, TextArrangement: #TextOnly }; genre @title: '{i18n>Genre}' @Common: { Text: genre.name, TextArrangement: #TextOnly };
author @title: '{i18n>Author}' @Common: { Text: author.name, TextArrangement: #TextOnly }; author @title: '{i18n>Author}' @Common: { Text: author.name, TextArrangement: #TextOnly };
price @title: '{i18n>Price}' @Measures.ISOCurrency : currency_code; price @title: '{i18n>Price}' @Measures.ISOCurrency : currency;
stock @title: '{i18n>Stock}'; stock @title: '{i18n>Stock}';
descr @title: '{i18n>Description}' @UI.MultiLineText; descr @title: '{i18n>Description}' @UI.MultiLineText;
image @title: '{i18n>Image}'; image @title: '{i18n>Image}';

View File

@@ -1 +0,0 @@
// just a dummy tag file to be identified as a plugin

View File

@@ -1,12 +0,0 @@
{
"name": "@cap-js/ord",
"version": "2.0.0",
"cds": {
"requires": {
"SAP ORD Service": {
"model": "@cap-js/ord/srv/ord-service",
"service": "OrdService"
}
}
}
}

View File

@@ -1,12 +0,0 @@
// @requires: 'ORDconsumer'
@rest @path:'/ord/v1'
service OrdService {
@readonly entity documents {
key id: String;
}
@readonly entity csn {
key id: String;
}
function api (service: String, format: String) returns {};
}

View File

@@ -1,73 +0,0 @@
import cds from '@sap/cds'
export class OrdService extends cds.ApplicationService {
init(){
this.on('READ','documents', req => {
let csn = cds.context?.model || cds.model
return { ord: csn }
})
/**
* Just an example to do something with id, if given.
* Try it out with URLs like that:
*
* - http://localhost:4004/ord/v1/documents
* - http://localhost:4004/ord/v1/documents/CatalogService
* - http://localhost:4004/ord/v1/documents/CatalogService.Books
* - http://localhost:4004/ord/v1/documents/CatalogService.Authors
*/
this.on('READ','csn', req => {
let csn = cds.context?.model || cds.model
let { id } = req.data
if (id) csn = csn.definitions[id] || 'not in model!'
return { id, csn }
})
/**
* Just an example to serve arbitrary content with a function.
* Try it out with URLs like that:
*
* - http://localhost:4004/ord/v1/api?service=CatalogService
* - http://localhost:4004/ord/v1/api?service=CatalogService&format=edmx
* - http://localhost:4004/ord/v1/api?service=CatalogService&format=edmx-v2
* - http://localhost:4004/ord/v1/api?service=CatalogService&format=openapi
*/
this.on('api', req => {
let csn = cds.context?.model || cds.model
let { service, format = 'csn' } = req.data
let { res } = req.http
if (format === 'csn') {
if (!service) return res.send(csn)
service = csn.services[service]
return res.send({ definitions: [ service, ...service.entities ] .reduce ((all,e) => {
let d = all[e.name] = {...e}
delete d.projection // not part of the API
delete d.query // not part of the API
return all
},{})})
}
let api = cds.compile(csn).to[format]({service})
return res.send(api)
})
/**
* Example how to register arbitrary express routes,
* and map them to our service's interface.
* Try it out with URLs like that:
*
* - http://localhost:4004/ord/v1/csn/CatalogService
* - http://localhost:4004/ord/v1/edmx/CatalogService
* - http://localhost:4004/ord/v1/openapi/CatalogService
* - http://localhost:4004/ord/v1/asyncapi/CatalogService
*
* NOTE: we add cds.middlewares.before to the route, which gives us all
* the context and auth handling, which is also available to CAP services.
*/
cds.app.get (`${this.path}/:api?/:service?`, cds.middlewares.before, req => {
const { api, service } = req.params
return this.api (service, api)
})
return super.init()
}
}

111
package-lock.json generated
View File

@@ -108,14 +108,14 @@
} }
}, },
"node_modules/@cap-js-community/odata-v2-adapter": { "node_modules/@cap-js-community/odata-v2-adapter": {
"version": "1.14.0", "version": "1.13.8",
"resolved": "https://registry.npmjs.org/@cap-js-community/odata-v2-adapter/-/odata-v2-adapter-1.14.0.tgz", "resolved": "https://registry.npmjs.org/@cap-js-community/odata-v2-adapter/-/odata-v2-adapter-1.13.8.tgz",
"integrity": "sha512-L4yIHml7Pc3dpSiCCJkOADje7kXgMvAIhFx6ZdeWwBnniPiul++8zCbRMIIjjwFKuujTwjoprb7C4IzZdYlAfQ==", "integrity": "sha512-IYkUUJLMS8sNL+6H8NOT17pMKweItjEUqV9EctuHw8+pWsFLcK3GehztZrtPS9DfEBOD9tC/aPNU5+b5unmZrQ==",
"license": "Apache-2.0", "license": "Apache-2.0",
"dependencies": { "dependencies": {
"body-parser": "^1.20.3", "body-parser": "^1.20.3",
"body-parser-xml": "^2.0.5", "body-parser-xml": "^2.0.5",
"express": "^4.21.2", "express": "^4.21.1",
"express-fileupload": "^1.5.1", "express-fileupload": "^1.5.1",
"http-proxy-middleware": "^3.0.3", "http-proxy-middleware": "^3.0.3",
"xml2js": "^0.6.2" "xml2js": "^0.6.2"
@@ -125,15 +125,17 @@
} }
}, },
"node_modules/@cap-js/cds-types": { "node_modules/@cap-js/cds-types": {
"version": "0.9.0", "version": "0.8.0",
"resolved": "https://registry.npmjs.org/@cap-js/cds-types/-/cds-types-0.9.0.tgz", "resolved": "https://registry.npmjs.org/@cap-js/cds-types/-/cds-types-0.8.0.tgz",
"integrity": "sha512-AD4WGAOOSszaleQQqheIo0hHm50zk3NejMlHsuG6cLh4EyK/kozvcx8hkWfAkUT/s11fa8OjyMhztFCy8b5DAA==", "integrity": "sha512-iy+Rc4C6tnFuBwTIREcrFBVp0vKVN+iB5WoZFcBX7b5y7rUvK9Pz/5YHplacyQpwzxUc8Iv+CXG6LeWH/b7Qqw==",
"dev": true, "dev": true,
"hasInstallScript": true, "hasInstallScript": true,
"license": "SEE LICENSE IN LICENSE", "license": "SEE LICENSE IN LICENSE",
"dependencies": {
"@types/express": "^4.17.21"
},
"peerDependencies": { "peerDependencies": {
"@sap/cds": "^8.0.0", "@sap/cds": "^8.0.0"
"@types/express": ">=4"
} }
}, },
"node_modules/@cap-js/db-service": { "node_modules/@cap-js/db-service": {
@@ -150,9 +152,9 @@
} }
}, },
"node_modules/@cap-js/sqlite": { "node_modules/@cap-js/sqlite": {
"version": "1.7.8", "version": "1.7.7",
"resolved": "https://registry.npmjs.org/@cap-js/sqlite/-/sqlite-1.7.8.tgz", "resolved": "https://registry.npmjs.org/@cap-js/sqlite/-/sqlite-1.7.7.tgz",
"integrity": "sha512-llFn0LGNIdlsfU4KjzyuIMvlQhKxXodq4GIt9yStmmX/av/twwHR8SyUmTJirRH4IkNtpCsuNYpsI+bYO2Xklg==", "integrity": "sha512-SOmFJMr6pjWOniFRkJsrI0BpQEQT5Q8o+IVZ/LXFj6+bAe0NQQztzxhMQx62V/Px3u58JJM3xkPMU+QC5PcJHw==",
"dev": true, "dev": true,
"license": "SEE LICENSE", "license": "SEE LICENSE",
"dependencies": { "dependencies": {
@@ -257,14 +259,11 @@
} }
}, },
"node_modules/@eslint/core": { "node_modules/@eslint/core": {
"version": "0.10.0", "version": "0.9.0",
"resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.10.0.tgz", "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.9.0.tgz",
"integrity": "sha512-gFHJ+xBOo4G3WRlR1e/3G8A6/KZAH6zcE/hkLRCZTi/B9avAG365QhFA8uOGzTMqgTghpn7/fSnscW++dpMSAw==", "integrity": "sha512-7ATR9F0e4W85D/0w7cU0SNj7qkAexMG+bAHEZOjo9akvGuhHE2m7umzWzfnpa0XAg5Kxc1BWmtPMV67jJ+9VUg==",
"dev": true, "dev": true,
"license": "Apache-2.0", "license": "Apache-2.0",
"dependencies": {
"@types/json-schema": "^7.0.15"
},
"engines": { "engines": {
"node": "^18.18.0 || ^20.9.0 || >=21.1.0" "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
} }
@@ -294,9 +293,9 @@
} }
}, },
"node_modules/@eslint/js": { "node_modules/@eslint/js": {
"version": "9.19.0", "version": "9.16.0",
"resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.19.0.tgz", "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.16.0.tgz",
"integrity": "sha512-rbq9/g38qjfqFLOVPvwjIvFFdNziEC5S65jmjPw5r6A//QH+W91akh9irMwjDN8zKUTak6W9EsAv4m/7Wnw0UQ==", "integrity": "sha512-tw2HxzQkrbeuvyj1tG2Yqq+0H9wGoI2IMk4EOsQeX+vmd75FtJAzf+gTA69WF+baUKRYQ3x2kbLE08js5OsTVg==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"engines": { "engines": {
@@ -314,13 +313,12 @@
} }
}, },
"node_modules/@eslint/plugin-kit": { "node_modules/@eslint/plugin-kit": {
"version": "0.2.5", "version": "0.2.3",
"resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.2.5.tgz", "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.2.3.tgz",
"integrity": "sha512-lB05FkqEdUg2AA0xEbUz0SnkXT1LcCTa438W4IWTUh4hdOnVbQyOJ81OrDXsJk/LSiJHubgGEFoR5EHq1NsH1A==", "integrity": "sha512-2b/g5hRmpbb1o4GnTZax9N9m0FXzz9OV42ZzI4rDDMDuHUqigAiQCEWChBWCY4ztAGVRjoWT19v0yMmc5/L5kA==",
"dev": true, "dev": true,
"license": "Apache-2.0", "license": "Apache-2.0",
"dependencies": { "dependencies": {
"@eslint/core": "^0.10.0",
"levn": "^0.4.1" "levn": "^0.4.1"
}, },
"engines": { "engines": {
@@ -391,9 +389,9 @@
} }
}, },
"node_modules/@sap/cds": { "node_modules/@sap/cds": {
"version": "8.6.1", "version": "8.5.0",
"resolved": "https://registry.npmjs.org/@sap/cds/-/cds-8.6.1.tgz", "resolved": "https://registry.npmjs.org/@sap/cds/-/cds-8.5.0.tgz",
"integrity": "sha512-JYHRrGs6Tgle5Vmj/o3BaQkOBVcroweOrXhhiUVH6twISy+Yi2cWZdTr0EFFEt94FI1dVqvrVnEM67jEjOQImQ==", "integrity": "sha512-mRiLBPcY5vC1xi21pPqzMtp1HY9FOOFajkiuSArqnwtiyp2fxqWVombuVE9lTB2UyBl9bL8XXwrpjzDftIbnBg==",
"license": "SEE LICENSE IN LICENSE", "license": "SEE LICENSE IN LICENSE",
"dependencies": { "dependencies": {
"@sap/cds-compiler": ">=5.1", "@sap/cds-compiler": ">=5.1",
@@ -466,7 +464,6 @@
"integrity": "sha512-fB3Zu92ucau0iQ0JMCFQE7b/dv8Ot07NI3KaZIkIUNXq82k4eBAqUaneXfleGY9JWskeS9y+u0nXMyspcuQrCg==", "integrity": "sha512-fB3Zu92ucau0iQ0JMCFQE7b/dv8Ot07NI3KaZIkIUNXq82k4eBAqUaneXfleGY9JWskeS9y+u0nXMyspcuQrCg==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"peer": true,
"dependencies": { "dependencies": {
"@types/connect": "*", "@types/connect": "*",
"@types/node": "*" "@types/node": "*"
@@ -478,7 +475,6 @@
"integrity": "sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug==", "integrity": "sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"peer": true,
"dependencies": { "dependencies": {
"@types/node": "*" "@types/node": "*"
} }
@@ -495,7 +491,6 @@
"integrity": "sha512-ejlPM315qwLpaQlQDTjPdsUFSc6ZsP4AN6AlWnogPjQ7CVi7PYF3YVz+CY3jE2pwYf7E/7HlDAN0rV2GxTG0HQ==", "integrity": "sha512-ejlPM315qwLpaQlQDTjPdsUFSc6ZsP4AN6AlWnogPjQ7CVi7PYF3YVz+CY3jE2pwYf7E/7HlDAN0rV2GxTG0HQ==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"peer": true,
"dependencies": { "dependencies": {
"@types/body-parser": "*", "@types/body-parser": "*",
"@types/express-serve-static-core": "^4.17.33", "@types/express-serve-static-core": "^4.17.33",
@@ -509,7 +504,6 @@
"integrity": "sha512-y6W03tvrACO72aijJ5uF02FRq5cgDR9lUxddQ8vyF+GvmjJQqbzDcJngEjURc+ZsG31VI3hODNZJ2URj86pzmg==", "integrity": "sha512-y6W03tvrACO72aijJ5uF02FRq5cgDR9lUxddQ8vyF+GvmjJQqbzDcJngEjURc+ZsG31VI3hODNZJ2URj86pzmg==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"peer": true,
"dependencies": { "dependencies": {
"@types/node": "*", "@types/node": "*",
"@types/qs": "*", "@types/qs": "*",
@@ -522,8 +516,7 @@
"resolved": "https://registry.npmjs.org/@types/http-errors/-/http-errors-2.0.4.tgz", "resolved": "https://registry.npmjs.org/@types/http-errors/-/http-errors-2.0.4.tgz",
"integrity": "sha512-D0CFMMtydbJAegzOyHjtiKPLlvnm3iTZyZRSZoLq2mRhDdmLfIWOCYPfQJ4cu2erKghU++QvjcUjp/5h7hESpA==", "integrity": "sha512-D0CFMMtydbJAegzOyHjtiKPLlvnm3iTZyZRSZoLq2mRhDdmLfIWOCYPfQJ4cu2erKghU++QvjcUjp/5h7hESpA==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT"
"peer": true
}, },
"node_modules/@types/http-proxy": { "node_modules/@types/http-proxy": {
"version": "1.17.15", "version": "1.17.15",
@@ -544,8 +537,7 @@
"resolved": "https://registry.npmjs.org/@types/mime/-/mime-1.3.5.tgz", "resolved": "https://registry.npmjs.org/@types/mime/-/mime-1.3.5.tgz",
"integrity": "sha512-/pyBZWSLD2n0dcHE3hq8s8ZvcETHtEuF+3E7XVt0Ig2nvsVQXdghHVcEkIWjy9A0wKfTn97a/PSDYohKIlnP/w==", "integrity": "sha512-/pyBZWSLD2n0dcHE3hq8s8ZvcETHtEuF+3E7XVt0Ig2nvsVQXdghHVcEkIWjy9A0wKfTn97a/PSDYohKIlnP/w==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT"
"peer": true
}, },
"node_modules/@types/node": { "node_modules/@types/node": {
"version": "22.5.5", "version": "22.5.5",
@@ -561,16 +553,14 @@
"resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.9.16.tgz", "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.9.16.tgz",
"integrity": "sha512-7i+zxXdPD0T4cKDuxCUXJ4wHcsJLwENa6Z3dCu8cfCK743OGy5Nu1RmAGqDPsoTDINVEcdXKRvR/zre+P2Ku1A==", "integrity": "sha512-7i+zxXdPD0T4cKDuxCUXJ4wHcsJLwENa6Z3dCu8cfCK743OGy5Nu1RmAGqDPsoTDINVEcdXKRvR/zre+P2Ku1A==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT"
"peer": true
}, },
"node_modules/@types/range-parser": { "node_modules/@types/range-parser": {
"version": "1.2.7", "version": "1.2.7",
"resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.7.tgz", "resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.7.tgz",
"integrity": "sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ==", "integrity": "sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT"
"peer": true
}, },
"node_modules/@types/send": { "node_modules/@types/send": {
"version": "0.17.4", "version": "0.17.4",
@@ -578,7 +568,6 @@
"integrity": "sha512-x2EM6TJOybec7c52BX0ZspPodMsQUd5L6PRwOunVyVUhXiBSKf3AezDL8Dgvgt5o0UfKNfuA0eMLr2wLT4AiBA==", "integrity": "sha512-x2EM6TJOybec7c52BX0ZspPodMsQUd5L6PRwOunVyVUhXiBSKf3AezDL8Dgvgt5o0UfKNfuA0eMLr2wLT4AiBA==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"peer": true,
"dependencies": { "dependencies": {
"@types/mime": "^1", "@types/mime": "^1",
"@types/node": "*" "@types/node": "*"
@@ -590,7 +579,6 @@
"integrity": "sha512-W8Ym+h8nhuRwaKPaDw34QUkwsGi6Rc4yYqvKFo5rm2FUEhCFbzVWrxXUxuKK8TASjWsysJY0nsmNCGhCOIsrOw==", "integrity": "sha512-W8Ym+h8nhuRwaKPaDw34QUkwsGi6Rc4yYqvKFo5rm2FUEhCFbzVWrxXUxuKK8TASjWsysJY0nsmNCGhCOIsrOw==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"peer": true,
"dependencies": { "dependencies": {
"@types/http-errors": "*", "@types/http-errors": "*",
"@types/node": "*", "@types/node": "*",
@@ -706,9 +694,9 @@
"license": "MIT" "license": "MIT"
}, },
"node_modules/axios": { "node_modules/axios": {
"version": "1.7.9", "version": "1.7.8",
"resolved": "https://registry.npmjs.org/axios/-/axios-1.7.9.tgz", "resolved": "https://registry.npmjs.org/axios/-/axios-1.7.8.tgz",
"integrity": "sha512-LhLcE7Hbiryz8oMDdDptSrWowmB4Bl6RCt6sIJKpRB4XtVf0iEgewX3au/pJqm+Py1kCASkb/FFKjxQaLtxJvw==", "integrity": "sha512-Uu0wb7KNqK2t5K+YQyVCLM76prD5sRFjKHbJYCP1J7JFGEQ6nN7HWn9+04LAeiJ3ji54lgS/gZCH1oxyrf1SPw==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
@@ -1315,19 +1303,19 @@
} }
}, },
"node_modules/eslint": { "node_modules/eslint": {
"version": "9.19.0", "version": "9.16.0",
"resolved": "https://registry.npmjs.org/eslint/-/eslint-9.19.0.tgz", "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.16.0.tgz",
"integrity": "sha512-ug92j0LepKlbbEv6hD911THhoRHmbdXt2gX+VDABAW/Ir7D3nqKdv5Pf5vtlyY6HQMTEP2skXY43ueqTCWssEA==", "integrity": "sha512-whp8mSQI4C8VXd+fLgSM0lh3UlmcFtVwUQjyKCFfsp+2ItAIYhlq/hqGahGqHE6cv9unM41VlqKk2VtKYR2TaA==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@eslint-community/eslint-utils": "^4.2.0", "@eslint-community/eslint-utils": "^4.2.0",
"@eslint-community/regexpp": "^4.12.1", "@eslint-community/regexpp": "^4.12.1",
"@eslint/config-array": "^0.19.0", "@eslint/config-array": "^0.19.0",
"@eslint/core": "^0.10.0", "@eslint/core": "^0.9.0",
"@eslint/eslintrc": "^3.2.0", "@eslint/eslintrc": "^3.2.0",
"@eslint/js": "9.19.0", "@eslint/js": "9.16.0",
"@eslint/plugin-kit": "^0.2.5", "@eslint/plugin-kit": "^0.2.3",
"@humanfs/node": "^0.16.6", "@humanfs/node": "^0.16.6",
"@humanwhocodes/module-importer": "^1.0.1", "@humanwhocodes/module-importer": "^1.0.1",
"@humanwhocodes/retry": "^0.4.1", "@humanwhocodes/retry": "^0.4.1",
@@ -1335,7 +1323,7 @@
"@types/json-schema": "^7.0.15", "@types/json-schema": "^7.0.15",
"ajv": "^6.12.4", "ajv": "^6.12.4",
"chalk": "^4.0.0", "chalk": "^4.0.0",
"cross-spawn": "^7.0.6", "cross-spawn": "^7.0.5",
"debug": "^4.3.2", "debug": "^4.3.2",
"escape-string-regexp": "^4.0.0", "escape-string-regexp": "^4.0.0",
"eslint-scope": "^8.2.0", "eslint-scope": "^8.2.0",
@@ -1491,10 +1479,9 @@
} }
}, },
"node_modules/express": { "node_modules/express": {
"version": "4.21.2", "version": "4.21.1",
"resolved": "https://registry.npmjs.org/express/-/express-4.21.2.tgz", "resolved": "https://registry.npmjs.org/express/-/express-4.21.1.tgz",
"integrity": "sha512-28HqgMZAmih1Czt9ny7qr6ek2qddF4FclbMzwhCREB6OFfH+rXAnuNCwo1/wFvrtbgsQDb4kSbX9de9lFbrXnA==", "integrity": "sha512-YSFlK1Ee0/GC8QaO91tHcDxJiE/X4FbpAyQWkxAvG6AXCuR65YzK8ua6D9hvi/TzUfZMpc+BwuM1IPw8fmQBiQ==",
"license": "MIT",
"dependencies": { "dependencies": {
"accepts": "~1.3.8", "accepts": "~1.3.8",
"array-flatten": "1.1.1", "array-flatten": "1.1.1",
@@ -1515,7 +1502,7 @@
"methods": "~1.1.2", "methods": "~1.1.2",
"on-finished": "2.4.1", "on-finished": "2.4.1",
"parseurl": "~1.3.3", "parseurl": "~1.3.3",
"path-to-regexp": "0.1.12", "path-to-regexp": "0.1.10",
"proxy-addr": "~2.0.7", "proxy-addr": "~2.0.7",
"qs": "6.13.0", "qs": "6.13.0",
"range-parser": "~1.2.1", "range-parser": "~1.2.1",
@@ -1530,10 +1517,6 @@
}, },
"engines": { "engines": {
"node": ">= 0.10.0" "node": ">= 0.10.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/express"
} }
}, },
"node_modules/express-fileupload": { "node_modules/express-fileupload": {
@@ -2458,9 +2441,9 @@
} }
}, },
"node_modules/path-to-regexp": { "node_modules/path-to-regexp": {
"version": "0.1.12", "version": "0.1.10",
"resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.12.tgz", "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.10.tgz",
"integrity": "sha512-RA1GjUVMnvYFxuqovrEqZoxxW5NUZqbwKtYz/Tt7nXerk0LbLblQmrsgdeOxV5SFHf0UDggjS/bSeOZwt1pmEQ==", "integrity": "sha512-7lf7qcQidTku0Gu3YDPc8DJ1q7OOucfa/BSsIwjuh56VU7katFvuM8hULfkwB3Fns/rsVF7PwPKVw1sl5KQS9w==",
"license": "MIT" "license": "MIT"
}, },
"node_modules/pathval": { "node_modules/pathval": {

View File

@@ -15,7 +15,6 @@
"./fiori", "./fiori",
"./hello", "./hello",
"./media", "./media",
"./ord",
"./orders", "./orders",
"./loggers", "./loggers",
"./reviews" "./reviews"

View File

@@ -27,15 +27,15 @@ describe('cap/samples - Localized Data', () => {
}) })
it('supports queries with $expand', async () => { it('supports queries with $expand', async () => {
const { data } = await GET(`/browse/Books?&$select=title,author&$expand=currency`, { const { data } = await GET(`/browse/Book?&$select=title,author,currencyName`, {
headers: { 'Accept-Language': 'de' }, headers: { 'Accept-Language': 'de' },
}) })
expect(data.value).to.containSubset([ expect(data.value).to.containSubset([
{ title: 'Sturmhöhe', author: 'Emily Brontë', currency: { name: 'Pfund' } }, { title: 'Sturmhöhe', author: 'Emily Brontë', currencyName: 'Pfund' },
{ title: 'Jane Eyre', author: 'Charlotte Brontë', currency: { name: 'Pfund' } }, { title: 'Jane Eyre', author: 'Charlotte Brontë', currencyName: 'Pfund' },
{ title: 'The Raven', author: 'Edgar Allen Poe', currency: { name: 'US-Dollar' } }, { title: 'The Raven', author: 'Edgar Allen Poe', currencyName: 'US-Dollar' },
{ title: 'Eleonora', author: 'Edgar Allen Poe', currency: { name: 'US-Dollar' } }, { title: 'Eleonora', author: 'Edgar Allen Poe', currencyName: 'US-Dollar' },
{ title: 'Catweazle', author: 'Richard Carpenter', currency: { name: 'Yen' } }, { title: 'Catweazle', author: 'Richard Carpenter', currencyName: 'Yen' },
]) ])
}) })

View File

@@ -12,20 +12,20 @@ describe('cap/samples - Bookshop APIs', () => {
'odata-version': '4.0', 'odata-version': '4.0',
}) })
expect(headers['content-type']).to.match(/application\/xml/) expect(headers['content-type']).to.match(/application\/xml/)
expect(data).to.contain('<EntitySet Name="Books" EntityType="CatalogService.Books">') expect(data).to.contain('<EntitySet Name="Books" EntityType="CatalogService.Books"/>')
expect(data).to.contain('<Annotation Term="Common.Label" String="Currency"/>') expect(data).to.contain('<Annotation Term="Common.Label" String="Currency Symbol"/>')
}) })
it('serves ListOfBooks?$expand=genre,currency', async () => { it('serves Books?$expand=genre,currency', async () => {
const Mystery = { ID: 16, name: 'Mystery', descr: null, parent_ID: 10 } const Mystery = { ID: 16, name: 'Mystery', descr: null, parent_ID: 10 }
const Romance = { ID: 15, name: 'Romance', descr: null, parent_ID: 10 } const Romance = { ID: 15, name: 'Romance', descr: null, parent_ID: 10 }
const USD = { code: 'USD', name: 'US Dollar', descr: null, symbol: '$' } const USD = { code: 'USD', name: 'US Dollar', descr: null, symbol: '$' }
const { data } = await GET `/browse/ListOfBooks ${{ const { data } = await GET `/admin/Books ${{
params: { $search: 'Po', $select: `title,author`, $expand:`genre,currency` }, params: { $search: 'Po', $select: `title,author`, $expand:`genre,currency` },
}}` }}`
expect(data.value).to.containSubset([ expect(data.value).to.containSubset([
{ ID: 251, title: 'The Raven', author: 'Edgar Allen Poe', genre:Mystery, currency:USD }, { ID: 251, title: 'The Raven', author_ID: 150, genre:Mystery, currency:USD },
{ ID: 252, title: 'Eleonora', author: 'Edgar Allen Poe', genre:Romance, currency:USD }, { ID: 252, title: 'Eleonora', author_ID: 150, genre:Romance, currency:USD },
]) ])
}) })