diff --git a/.github/actions/test-sample/action.yml b/.github/actions/test-sample/action.yml
new file mode 100644
index 000000000..d881b42fd
--- /dev/null
+++ b/.github/actions/test-sample/action.yml
@@ -0,0 +1,27 @@
+name: 'Test Sample'
+description: 'Compile sample and run tests'
+
+runs:
+ using: 'composite'
+ steps:
+ - name: Set up Java ${{ inputs.java-version }}
+ uses: actions/setup-java@v4
+ with:
+ java-version: ${{ inputs.java-version }}
+ distribution: sapmachine
+ cache: maven
+
+ - name: Set up Maven ${{ inputs.maven-version }}
+ uses: stCarolas/setup-maven@v5
+ with:
+ maven-version: ${{ inputs.maven-version }}
+
+ - name: Compile sample
+ shell: bash
+ working-directory: samples/bookshop
+ run: mvn compile test-compile -B -V
+
+ - name: Run tests
+ shell: bash
+ working-directory: samples/bookshop
+ run: mvn test
\ No newline at end of file
diff --git a/.github/workflows/pull-request-build.yml b/.github/workflows/pull-request-build.yml
index 0c6a03e95..7848bae84 100644
--- a/.github/workflows/pull-request-build.yml
+++ b/.github/workflows/pull-request-build.yml
@@ -46,7 +46,13 @@ jobs:
with:
java-version: ${{ matrix.java-version }}
maven-version: ${{ env.MAVEN_VERSION }}
-
+
+ - name: Build sample and run tests
+ uses: ./.github/actions/test-sample
+ with:
+ java-version: ${{ matrix.java-version }}
+ maven-version: ${{ env.MAVEN_VERSION }}
+
- name: SonarQube Scan
uses: ./.github/actions/scan-with-sonar
if: ${{ matrix.java-version == 17 }}
diff --git a/.gitignore b/.gitignore
index 521007f14..0f7c96eec 100644
--- a/.gitignore
+++ b/.gitignore
@@ -33,3 +33,5 @@ event.json
# CAP Notebook
cap-notebook/demoapp/
+
+.DS_Store
\ No newline at end of file
diff --git a/.pipeline/config.yml b/.pipeline/config.yml
index aa4f2a60f..6ef0e6c1d 100644
--- a/.pipeline/config.yml
+++ b/.pipeline/config.yml
@@ -35,5 +35,22 @@ steps:
- sonar.qualitygate.wait=true
- sonar.java.source=17
- sonar.exclusions=**/node_modules/**,**/target/**,**/test/**
- - sonar.coverage.jacoco.xmlReportPaths=cds-feature-attachments/target/site/jacoco/jacoco.xml,storage-targets/cds-feature-attachments-oss/target/site/jacoco/jacoco.xml
- - sonar.coverage.exclusions=cds-feature-attachments/src/test/**,cds-feature-attachments/src/gen/**,integration-tests/**,storage-targets/cds-feature-attachments-fs/**,storage-targets/cds-feature-attachments-oss/src/test/**
+ - sonar.modules=cds-feature-attachments,cds-feature-attachments-fs,cds-feature-attachments-oss,bookshop
+ - sonar.coverage.jacoco.xmlReportPaths=cds-feature-attachments/target/site/jacoco/jacoco.xml,storage-targets/cds-feature-attachments-oss/target/site/jacoco/jacoco.xml,samples/bookshop/srv/target/site/jacoco/jacoco.xml
+ - sonar.coverage.exclusions=cds-feature-attachments/src/test/**,cds-feature-attachments/src/gen/**,storage-targets/cds-feature-attachments-fs/**,storage-targets/cds-feature-attachments-oss/src/test/**,samples/bookshop/srv/src/test/**,samples/bookshop/srv/src/gen/**
+ - cds-feature-attachments.sonar.projectBaseDir=cds-feature-attachments
+ - cds-feature-attachments.sonar.sources=src/main/java
+ - cds-feature-attachments.sonar.tests=src/test/java
+ - cds-feature-attachments.sonar.java.binaries=target/classes
+ - cds-feature-attachments-fs.sonar.projectBaseDir=storage-targets/cds-feature-attachments-fs
+ - cds-feature-attachments-fs.sonar.sources=src/main/java
+ - cds-feature-attachments-fs.sonar.tests=src/test/java
+ - cds-feature-attachments-fs.sonar.java.binaries=target/classes
+ - cds-feature-attachments-oss.sonar.projectBaseDir=storage-targets/cds-feature-attachments-oss
+ - cds-feature-attachments-oss.sonar.sources=src/main/java
+ - cds-feature-attachments-oss.sonar.tests=src/test/java
+ - cds-feature-attachments-oss.sonar.java.binaries=target/classes
+ - bookshop.sonar.projectBaseDir=samples/bookshop/srv
+ - bookshop.sonar.sources=src/main/java
+ - bookshop.sonar.tests=src/test/java
+ - bookshop.sonar.java.binaries=target/classes
diff --git a/README.md b/README.md
index d58043eb0..5fe8ba67e 100644
--- a/README.md
+++ b/README.md
@@ -1,4 +1,4 @@
-[](https://github.com/cap-java/cds-feature-attachments/actions/workflows/main-build.yml) [](https://github.com/cap-java/cds-feature-attachments/actions/workflows/main-build.yml) [](https://api.reuse.software/info/github.com/cap-java/cds-feature-attachments)
+\*\*\*\*[](https://github.com/cap-java/cds-feature-attachments/actions/workflows/main-build.yml) [](https://github.com/cap-java/cds-feature-attachments/actions/workflows/main-build.yml) [](https://api.reuse.software/info/github.com/cap-java/cds-feature-attachments)
# Attachments Plugin for SAP Cloud Application Programming Model (CAP)
@@ -16,6 +16,7 @@ It supports the [AWS, Azure, and Google object stores](storage-targets/cds-featu
* [Usage](#usage)
* [MVN Setup](#mvn-setup)
* [Changes in the CDS Models and for the UI](#changes-in-the-cds-models-and-for-the-UI)
+ * [Try the Bookshop Sample](#try-the-bookshop-sample)
* [Storage Targets](#storage-targets)
* [Malware Scanner](#malware-scanner)
* [Outbox](#outbox)
@@ -24,22 +25,25 @@ It supports the [AWS, Azure, and Google object stores](storage-targets/cds-featu
* [HTTP Endpoint](#http-endpoint)
* [Security](#security)
* [Releases: Maven Central and Artifactory](#releases-maven-central-and-artifactory)
-* [Minimum UI and CAP Java Version](#minimum-ui5-and-cap-java-version)
+* [Minimum UI5 and CAP Java Version](#minimum-ui5-and-cap-java-version)
* [Architecture Overview](#architecture-overview)
* [Design](#design)
* [Multitenancy](#multitenancy)
* [Object Stores](#object-stores)
* [Model Texts](#model-texts)
-* [Monitoring & Logging](#monitoring--logging)
+* [Monitoring \& Logging](#monitoring--logging)
* [Support, Feedback, Contributing](#support-feedback-contributing)
-* [References & Links](#references--links)
+* [References \& Links](#references--links)
## Quick Start
For a quick setup with in-memory storage:
+
- Add the `cds-feature-attachments` Maven dependency to the `srv/pom.xml` and configure the `cds-maven-plugin` with the `resolve` goal as described in [MVN Setup](#mvn-setup).
- Extend the CDS model with the `Attachments` aspect and annotate the service for UI integration as explained in [Changes in the CDS Models and for the UI](#changes-in-the-cds-models-and-for-the-UI).
+For a complete working example, see the [bookshop sample](samples/bookshop/).
+
The [incidents app](https://github.com/cap-java/incidents-app/) provides a demonstration of how to use this plugin.
For object store integration, see [Amazon, Azure, and Google Object Stores](storage-targets/cds-feature-attachments-oss).
@@ -57,6 +61,7 @@ As described in the [CAP Java Documentation](https://cap.cloud.sap/docs/java/bui
${latest-version}
```
+
Additionally, the `cds-maven-plugin` must be configured with the `resolve` goal to ensure CDS models from dependencies are available.
For this, add the following to the `srv/pom.xml` before the entry `build` as well:
@@ -75,6 +80,7 @@ For this, add the following to the `srv/pom.xml` before the entry `build` as wel
```
+
After that, the aspect `Attachments` can be used in the application's CDS model.
### Changes in the CDS Models and for the UI
@@ -83,35 +89,49 @@ To use the aspect `Attachments` on an existing entity, the corresponding entity
The following example shows how to extend the entity `Incidents` in the `srv` module with an additional `attachments.cds` file, it also directly adds the respective UI Facet.
To use this file with the [incidents app](https://github.com/cap-java/incidents-app/), check out the source code, copy the [file from the xmpls folder](https://github.com/cap-java/incidents-app/blob/main/xmpls/attachments.cds) to the srv folder and run the app as explained in the [incidents app README](https://github.com/cap-java/incidents-app/blob/main/README.md).
- ```cds
- using { sap.capire.incidents as my } from '../db/schema';
- using { sap.attachments.Attachments } from 'com.sap.cds/cds-feature-attachments';
-
- extend my.Incidents with {
- attachments: Composition of many Attachments;
- }
-
- using { ProcessorService as service } from '../app/services';
- annotate service.Incidents with @(
- UI.Facets: [
- ...,
- {
- $Type : 'UI.ReferenceFacet',
- ID : 'AttachmentsFacet',
- Label : '{i18n>attachments}',
- Target : 'attachments/@UI.LineItem'
- }
- ]
- );
- ```
+```cds
+using { sap.capire.incidents as my } from '../db/schema';
+using { sap.attachments.Attachments } from 'com.sap.cds/cds-feature-attachments';
+extend my.Incidents with {
+ attachments: Composition of many Attachments;
+}
+using { ProcessorService as service } from '../app/services';
+annotate service.Incidents with @(
+ UI.Facets: [
+ ...,
+ {
+ $Type : 'UI.ReferenceFacet',
+ ID : 'AttachmentsFacet',
+ Label : '{i18n>attachments}',
+ Target : 'attachments/@UI.LineItem'
+ }
+ ]
+);
+```
The UI Facet can also be added directly after other UI Facets in a `cds` file in the `app` folder.
+### Try the Bookshop Sample
+
+The easiest way to get started is with the included [bookshop sample](samples/bookshop/):
+
+```bash
+cd samples/bookshop
+npm install
+mvn clean install
+mvn spring-boot:run
+```
+
+Then browse to http://localhost:8080/browse/index.html to see attachments in action.
+
+For detailed setup instructions and implementation details, see the [bookshop sample README](samples/bookshop/README.md).
+
### Storage Targets
By default, the plugin operates without a dedicated storage target, storing attachments directly in the [underlying database](cds-feature-attachments/src/main/resources/cds/com.sap.cds/cds-feature-attachments/attachments.cds#L17).
Other available storage targets:
+
- [Amazon, Azure, and Google Object Stores](storage-targets/cds-feature-attachments-oss)
- [local file system as a storage backend](storage-targets/cds-feature-attachments-fs) (only for testing scenarios)
@@ -161,13 +181,13 @@ attachments.
If there is no malware scanner available, the attachments are automatically marked as `Clean`.
Scan status codes:
+
- `Clean`: Only attachments with the status `Clean` are accessible.
- `Scanning`: Immediately after upload, the attachment is marked as `Scanning`. Depending on processing speed, it may already appear as `Clean` when the page is reloaded.
- `Unscanned`: Attachment is still unscanned.
- `Failed`: Scanning failed.
- `Infected`: The attachment is infected.
-
### Outbox
In this plugin the [persistent outbox](https://cap.cloud.sap/docs/java/outbox#persistent) is used to mark attachments as
@@ -179,7 +199,6 @@ the default outbox configuration.
If the default shall be used, nothing needs to be done.
-
### Restore Endpoint
The attachment service has an event `RESTORE_ATTACHMENTS`.
@@ -190,10 +209,11 @@ This event can be called with a timestamp to restore externally stored attachmen
Documents which are marked as deleted can be restored.
The use cases behind this feature are:
+
- Restoring attachments after a database backup is restored:
-When restoring a database backup, any attachments stored in external storage (object stores, etc.) also need to be restored to maintain data consistency.
+ When restoring a database backup, any attachments stored in external storage (object stores, etc.) also need to be restored to maintain data consistency.
- Restoring attachments that were marked as deleted:
-The restore endpoint provides a way to recover attachments that were previously marked as deleted, making it possible to undo deletions if needed.
+ The restore endpoint provides a way to recover attachments that were previously marked as deleted, making it possible to undo deletions if needed.
In the default implementation of the technical service `AttachmentService` this is not needed as the attachments are
stored directly in the database and are restored with the database.
@@ -288,13 +308,14 @@ the [CAP Java Documentation](https://cap.cloud.sap/docs/java/security).
## Minimum UI5 and CAP Java Version
| Component | Minimum Version |
-|-----------|-----------------|
+| --------- | --------------- |
| CAP Java | 3.10.3 |
| UI5 | 1.136.0 |
-
## Architecture Overview
+
### Design
+
- [Design Details](./doc/Design.md)
- [Process of Creating, Reading and Deleting an Attachment](./doc/Processes.md)
@@ -314,7 +335,7 @@ In the model, several fields are annotated with the `@title` annotation. Default
The following table gives an overview of the fields and the i18n codes:
| Field Name | i18n Code |
-|------------|-----------------------|
+| ---------- | --------------------- |
| `content` | `attachment_content` |
| `mimeType` | `attachment_mimeType` |
| `fileName` | `attachment_fileName` |
@@ -323,15 +344,15 @@ The following table gives an overview of the fields and the i18n codes:
In addition to the field names, header information (`@UI.HeaderInfo`) are also annotated:
-| Header Info | i18n Code |
-|------------------|---------------|
+| Header Info | i18n Code |
+| ---------------- | ------------- |
| `TypeName` | `attachment` |
| `TypeNamePlural` | `attachments` |
-
## Monitoring & Logging
To configure logging for the attachments plugin, add the following line to the `/srv/src/main/resources/application.yaml` of the consuming application:
+
```
logging:
level:
@@ -350,4 +371,5 @@ structure, as well as additional contribution information,
see our [Contribution Guidelines](./doc/CONTRIBUTING.md).
## References & Links
+
- [License](./LICENSE)
diff --git a/cap-notebook/README.md b/cap-notebook/README.md
deleted file mode 100644
index 097f7b1e3..000000000
--- a/cap-notebook/README.md
+++ /dev/null
@@ -1,31 +0,0 @@
-# Demo Application for Attachment Usage
-
-This project is a demo application for attachment usage in CAP Java.
-It is based on the [CAP Java Tutorial](https://cap.cloud.sap/docs/java/getting-started)
-and extends the tutorial with the usage of attachments.
-
-It is build with [CAP Notebooks](https://cap.cloud.sap/docs/tools/#cap-vscode-notebook).
-
-## Execution
-
-You can download the CAP notebook from the `cap-notebook/attachments-demo-app.capnb` file and run it in Visual Studio
-Code with the CAP plugins installed.
-It will create a new folder `demoapp` which includes a CAP Java project.
-
-It will also add a UI part.
-If the CAP notebook is finished you can start the project with the following commands:
-
-```sh
-cd srv
-mvn cds:watch
-```
-
-After the project is started you can access the application with the following URL:
-[http://localhost:8080/](http://localhost:8080/)
-
-## Version
-
-The file `cap-notebook/version.txt` contains the latest version of the project.
-The CAP notebook reads the version from the file and uses it in the project.
-
-If the file is not present the version is set to `1.0.2`.
diff --git a/cap-notebook/attachments-demo-app.capnb b/cap-notebook/attachments-demo-app.capnb
deleted file mode 100644
index 516eb2a0a..000000000
--- a/cap-notebook/attachments-demo-app.capnb
+++ /dev/null
@@ -1,164 +0,0 @@
-[
- {
- "kind": 1,
- "language": "markdown",
- "value": "# CDS Feature Attachments CAP Notebook\n\nThis CAP notebook creates a CAP Java demoapp with sample data and enhances the app with the CAP feature for attachments.\nAll needed enhancements are done. \nFor more information check the project [README](../README.md). ",
- "outputs": []
- },
- {
- "kind": 1,
- "language": "markdown",
- "value": "## Add the App with Sample Data\n`cds init` is used to create a basic CAP Java app with sample data.",
- "outputs": []
- },
- {
- "kind": 2,
- "language": "shell",
- "value": "cds init demoapp --add java,sample\n",
- "outputs": []
- },
- {
- "kind": 2,
- "language": "shell",
- "value": "cd demoapp",
- "outputs": []
- },
- {
- "kind": 1,
- "language": "markdown",
- "value": "## Add Enhancements for the Datamodel\nThe `books` entity will be enhanced with the `attachments` composition.\n\nTo be able to use the `cds-feature-attachments` datamodel a `pom.xml` needs to be added with the maven dependency for the feature.\nThe version for the dependency is taken from the file `version.txt`. \nThis file will be updated if a new version is created in the repository.\n\nOnce the `pom.xml` is available and the version is set a `mvn clean verify` is executed.\nWith the the `resolve` goal of the `cds-maven-plugin` is executed which copies the `cds`-files from the feature in the `target` folder of the `db` module.\n\nOnce available in the `target` folder it will be found and can be used in the data models.",
- "outputs": []
- },
- {
- "kind": 2,
- "language": "shell",
- "value": "%%writefile \"db/attachment-extension.cds\"\nusing {sap.capire.bookshop.Books} from './schema';\nusing {sap.attachments.Attachments, sap.attachments.StatusCode} from`com.sap.cds/cds-feature-attachments`;\n\nextend entity Books with {\n attachments : Composition of many Attachments;\n}\n\nentity Statuses @cds.autoexpose @readonly {\n key code : StatusCode;\n text : localized String(255);\n}\n\nextend Attachments with {\n statusText : Association to Statuses on statusText.code = $self.status;\n}\n\nannotate Books.attachments with {\n status @(\n Common.Text: {\n $value: ![statusText.text],\n ![@UI.TextArrangement]: #TextOnly\n },\n ValueList: {entity:'Statuses'},\n sap.value.list: 'fixed-values'\n );\n}\n",
- "outputs": []
- },
- {
- "kind": 2,
- "language": "shell",
- "value": "%%writefile \"db/data/Statuses.csv\"\ncode;text\nUnscanned;Unscanned Attachment\nScanning;Scanning Attachment\nClean;Clean Attachment\nInfected;Infected Attachment\nFailed;Scanning Failed",
- "outputs": []
- },
- {
- "kind": 2,
- "language": "shell",
- "value": "%%writefile \"db/data/Statuses_texts.csv\"\ncode;locale;text\nUnscanned;en;Unscanned Attachment\nScanning;en;Scanning Attachment\nClean;en;Clean Attachment\nInfected;en;Infected Attachment\nFailed;en;Scanning Failed",
- "outputs": []
- },
- {
- "kind": 2,
- "language": "shell",
- "value": "%%writefile \"db/pom.xml\"\n\n\n\t4.0.0\n\t\n\t\tdemoapp-parent\n\t\tcustomer\n\t\t${revision}\n\t\n\n\tdb\n\n \n \n \n com.sap.cds\n cds-feature-attachments\n attachment_version\n \n \n\t\n\t\n\t\t\n\t\t\t\n\t\t\t\tcom.sap.cds\n\t\t\t\tcds-maven-plugin\n\t\t\t\t${cds.services.version}\n\t\t\t\t\n\t\t\t\t\t\n\t\t\t\t\t\tcds.clean\n\t\t\t\t\t\t\n\t\t\t\t\t\t\tclean\n\t\t\t\t\t\t\n\t\t\t\t\t\n\t\t\t\t\t\n\t\t\t\t\t\tcds.resolve\n\t\t\t\t\t\t\n\t\t\t\t\t\t\tresolve\n\t\t\t\t\t\t\n\t\t\t\t\t\n\t\t\t\t\n\t\t\t\n\t\t\n\t\n\n",
- "outputs": []
- },
- {
- "kind": 2,
- "language": "shell",
- "value": "cd db",
- "outputs": []
- },
- {
- "kind": 2,
- "language": "java",
- "value": "Path versionPath = Paths.get(\"../../version.txt\");\nString version;\nif (Files.exists(versionPath)){\n version = Files.readString(versionPath);\n System.out.println(\"Using version from 'version.txt': \" + version);\n}else{\n version = \"1.0.2\";\n System.out.println(\"Using hard coded version: \" + version);\n}\nPath pomPath = Paths.get(\"pom.xml\");\nStream lines = Files.lines(pomPath);\nList replaced = lines.map(line -> line.replaceAll(\"attachment_version\", version)).collect(Collectors.toList());\nFiles.write(pomPath, replaced);\nlines.close();",
- "outputs": []
- },
- {
- "kind": 2,
- "language": "shell",
- "value": "mvn clean compile",
- "outputs": []
- },
- {
- "kind": 1,
- "language": "markdown",
- "value": "## Service Changes\n\nThe service module `srv` of the demo project needs to be updated with the maven dependency for `cds-feature-attachments`.\nThis dependency has included the logic to correctly handle attachments and call the `AtacchmentService`.\n\nAlso here, the version is taken from the `version.txt` which is updated in case a new version in the repository is created.",
- "outputs": []
- },
- {
- "kind": 2,
- "language": "shell",
- "value": "cd ../srv",
- "outputs": []
- },
- {
- "kind": 1,
- "language": "markdown",
- "value": "add the following dependency to the `srv/pom.xml`:\n```\n\n com.sap.cds\n cds-feature-attachments\n ${latest-version}\n\n``` ",
- "outputs": []
- },
- {
- "kind": 2,
- "language": "java",
- "value": "\nPath versionPath = Paths.get(\"../../version.txt\");\nString version;\nif (Files.exists(versionPath)){\n version = Files.readString(versionPath);\n System.out.println(\"Using version from 'version.txt': \" + version);\n}else{\n version = \"1.0.2\";\n System.out.println(\"Using hard coded version: \" + version);\n}\n\nString filePath = \"pom.xml\";\ntry {\n String pom = Files.readString(Path.of(filePath));\n String searchString = \"\";\n Pattern pattern = Pattern.compile(searchString);\n Matcher matcher = pattern.matcher(pom);\n\n if (matcher.find()) {\n System.out.println(\"String found at position: \" + matcher.start());\n } else {\n System.out.println(\"String not found\");\n }\n\n String newDependency = \"\\n\\n \\n com.sap.cds\\n cds-feature-attachments\\n \" + version + \"\\n \\n\\n\";\n int insertPos = matcher.end();\n pom = pom.substring(0, insertPos) + newDependency + pom.substring(insertPos);\n\n Files.writeString(Path.of(filePath), pom);\n\n} catch (IOException e) {\n e.printStackTrace();\n}",
- "outputs": []
- },
- {
- "kind": 2,
- "language": "shell",
- "value": "cd ..",
- "outputs": []
- },
- {
- "kind": 1,
- "language": "markdown",
- "value": "## UI Enhancements\n",
- "outputs": []
- },
- {
- "kind": 1,
- "language": "markdown",
- "value": "### UI Facet\n\nA UI facet is added for the attachments in the `AdminService`. Because the facet is only added in this service, only this services shows the attachments on the UI.\n\nThe following facet is added:\n\n```\n{\n $Type : 'UI.ReferenceFacet',\n ID : 'AttachmentsFacet',\n Label : '{i18n>attachments}',\n Target: 'attachments/@UI.LineItem'\\n \n}\n```",
- "outputs": []
- },
- {
- "kind": 2,
- "language": "java",
- "value": "String filePath = \"app/admin-books/fiori-service.cds\";\n\ntry {\n String cds = Files.readString(Path.of(filePath));\n String searchString = \"Target:\\\\s*'@UI\\\\.FieldGroup#Details'\\\\s*},\";\n Pattern pattern = Pattern.compile(searchString);\n Matcher matcher = pattern.matcher(cds);\n\n if (matcher.find()) {\n System.out.println(\"String found at position: \" + matcher.start());\n } else {\n System.out.println(\"String not found\");\n }\n\n String newFacet = \"\\n {\\n $Type : 'UI.ReferenceFacet',\\n ID : 'AttachmentsFacet',\\n Label : '{i18n>attachments}',\\n Target: 'attachments/@UI.LineItem'\\n },\";\n int insertPos = matcher.end();\n cds = cds.substring(0, insertPos) + newFacet + cds.substring(insertPos);\n\n Files.writeString(Path.of(filePath), cds);\n\n} catch (IOException e) {\n e.printStackTrace();\n}",
- "outputs": []
- },
- {
- "kind": 1,
- "language": "markdown",
- "value": "### Texts\n\nThe i18n property file is enhanced with the texts for the attachments to show correct texts on the UI.",
- "outputs": []
- },
- {
- "kind": 2,
- "language": "shell",
- "value": "cd app/_i18n",
- "outputs": []
- },
- {
- "kind": 2,
- "language": "java",
- "value": "String filePath = \"i18n.properties\";\n\nList properties = new ArrayList<>();\nproperties.add(\"\\n\");\nproperties.add(\"#Attachment properties\\n\");\nproperties.add(\"attachment_content = Content\\n\");\nproperties.add(\"attachment_mimeType = Mime Type\\n\");\nproperties.add(\"attachment_fileName = File Name\\n\");\nproperties.add(\"attachment_status = Status\\n\");\nproperties.add(\"attachment_note = Notes\\n\");\nproperties.add(\"attachment = Attachment\\n\");\nproperties.add(\"attachments = Attachments\");\n\nfor (String property: properties){\n try {\n Files.write(Paths.get(filePath), property.getBytes(), StandardOpenOption.APPEND);\n } catch (IOException e) {\n e.printStackTrace();\n }\n}\n",
- "outputs": []
- },
- {
- "kind": 1,
- "language": "markdown",
- "value": "## Build the Service\n\nRun `mvn clean compile` on the service to compile the models with all changes.",
- "outputs": []
- },
- {
- "kind": 2,
- "language": "shell",
- "value": "cd ../../srv\nmvn clean compile",
- "outputs": []
- },
- {
- "kind": 1,
- "language": "markdown",
- "value": "## Start the Service\n\n\nThe service can now be started with the following command in the `srv` module:\n\n```\nmvn cds:watch\n```\n\nAfter the service is startet the UI can be opened with:\n\n[http://localhost:8080](http://localhost:8080)\n\nNavigate to the index.html of the webapp and use user `admin` with password `admin`. \n\nUsing the tile `Manage Books` the attachments can be used in the detail area of the books.\n\nUsing the tile `Browse Books` no attachments are shown.",
- "outputs": []
- },
- {
- "kind": 1,
- "language": "markdown",
- "value": "",
- "outputs": []
- }
-]
\ No newline at end of file
diff --git a/cap-notebook/version.txt b/cap-notebook/version.txt
deleted file mode 100644
index 6085e9465..000000000
--- a/cap-notebook/version.txt
+++ /dev/null
@@ -1 +0,0 @@
-1.2.1
diff --git a/integration-tests/db/pom.xml b/integration-tests/db/pom.xml
index 6fa130147..e11cebc70 100644
--- a/integration-tests/db/pom.xml
+++ b/integration-tests/db/pom.xml
@@ -29,6 +29,9 @@
resolve
+
+ ${project.basedir}/..
+
diff --git a/integration-tests/pom.xml b/integration-tests/pom.xml
index 1d1ab7729..d3c22c463 100644
--- a/integration-tests/pom.xml
+++ b/integration-tests/pom.xml
@@ -6,6 +6,7 @@
com.sap.cds
cds-feature-attachments-root
${revision}
+ ../pom.xml
com.sap.cds.integration-tests
diff --git a/integration-tests/srv/pom.xml b/integration-tests/srv/pom.xml
index e96796080..6a5b6eada 100644
--- a/integration-tests/srv/pom.xml
+++ b/integration-tests/srv/pom.xml
@@ -83,6 +83,9 @@
resolve
+
+ ${project.basedir}/..
+
@@ -91,6 +94,7 @@
cds
+ ${project.basedir}/..
build --for java
deploy --to h2 --dry >
@@ -105,6 +109,7 @@
generate
+ ${project.basedir}/..
${generation-package}.integration.test.cds4j
diff --git a/integration-tests/srv/src/test/java/com/sap/cds/feature/attachments/integrationtests/testhandler/TestPersistenceHandlerTest.java b/integration-tests/srv/src/test/java/com/sap/cds/feature/attachments/integrationtests/testhandler/TestPersistenceHandlerTest.java
new file mode 100644
index 000000000..9dcf8875f
--- /dev/null
+++ b/integration-tests/srv/src/test/java/com/sap/cds/feature/attachments/integrationtests/testhandler/TestPersistenceHandlerTest.java
@@ -0,0 +1,116 @@
+/*
+ * © 2024-2024 SAP SE or an SAP affiliate company and cds-feature-attachments contributors.
+ */
+package com.sap.cds.feature.attachments.integrationtests.testhandler;
+
+import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import com.sap.cds.services.ServiceException;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+class TestPersistenceHandlerTest {
+
+ private TestPersistenceHandler testPersistenceHandler;
+
+ @BeforeEach
+ void setUp() {
+ testPersistenceHandler = new TestPersistenceHandler();
+ }
+
+ @Test
+ void testReset() {
+ // Set both flags to true
+ testPersistenceHandler.setThrowExceptionOnUpdate(true);
+ testPersistenceHandler.setThrowExceptionOnCreate(true);
+
+ // Reset should set both flags to false
+ testPersistenceHandler.reset();
+
+ // Verify no exceptions are thrown after reset
+ assertDoesNotThrow(() -> testPersistenceHandler.throwExceptionOnUpdate());
+ assertDoesNotThrow(() -> testPersistenceHandler.throwExceptionOnCreate());
+ }
+
+ @Test
+ void testThrowExceptionOnUpdateWhenEnabled() {
+ testPersistenceHandler.setThrowExceptionOnUpdate(true);
+
+ ServiceException exception =
+ assertThrows(ServiceException.class, () -> testPersistenceHandler.throwExceptionOnUpdate());
+
+ assertTrue(exception.getMessage().contains("Exception on update"));
+ }
+
+ @Test
+ void testThrowExceptionOnUpdateWhenDisabled() {
+ testPersistenceHandler.setThrowExceptionOnUpdate(false);
+
+ assertDoesNotThrow(() -> testPersistenceHandler.throwExceptionOnUpdate());
+ }
+
+ @Test
+ void testThrowExceptionOnCreateWhenEnabled() {
+ testPersistenceHandler.setThrowExceptionOnCreate(true);
+
+ ServiceException exception =
+ assertThrows(ServiceException.class, () -> testPersistenceHandler.throwExceptionOnCreate());
+
+ assertTrue(exception.getMessage().contains("Exception on create"));
+ }
+
+ @Test
+ void testThrowExceptionOnCreateWhenDisabled() {
+ testPersistenceHandler.setThrowExceptionOnCreate(false);
+
+ assertDoesNotThrow(() -> testPersistenceHandler.throwExceptionOnCreate());
+ }
+
+ @Test
+ void testSetThrowExceptionOnUpdate() {
+ // Test setting to true
+ testPersistenceHandler.setThrowExceptionOnUpdate(true);
+ assertThrows(ServiceException.class, () -> testPersistenceHandler.throwExceptionOnUpdate());
+
+ // Test setting to false
+ testPersistenceHandler.setThrowExceptionOnUpdate(false);
+ assertDoesNotThrow(() -> testPersistenceHandler.throwExceptionOnUpdate());
+ }
+
+ @Test
+ void testSetThrowExceptionOnCreate() {
+ // Test setting to true
+ testPersistenceHandler.setThrowExceptionOnCreate(true);
+ assertThrows(ServiceException.class, () -> testPersistenceHandler.throwExceptionOnCreate());
+
+ // Test setting to false
+ testPersistenceHandler.setThrowExceptionOnCreate(false);
+ assertDoesNotThrow(() -> testPersistenceHandler.throwExceptionOnCreate());
+ }
+
+ @Test
+ void testDefaultBehavior() {
+ // By default, both flags should be false
+ assertDoesNotThrow(() -> testPersistenceHandler.throwExceptionOnUpdate());
+ assertDoesNotThrow(() -> testPersistenceHandler.throwExceptionOnCreate());
+ }
+
+ @Test
+ void testIndependentFlagBehavior() {
+ // Test that the flags work independently
+ testPersistenceHandler.setThrowExceptionOnUpdate(true);
+ testPersistenceHandler.setThrowExceptionOnCreate(false);
+
+ assertThrows(ServiceException.class, () -> testPersistenceHandler.throwExceptionOnUpdate());
+ assertDoesNotThrow(() -> testPersistenceHandler.throwExceptionOnCreate());
+
+ // Switch them
+ testPersistenceHandler.setThrowExceptionOnUpdate(false);
+ testPersistenceHandler.setThrowExceptionOnCreate(true);
+
+ assertDoesNotThrow(() -> testPersistenceHandler.throwExceptionOnUpdate());
+ assertThrows(ServiceException.class, () -> testPersistenceHandler.throwExceptionOnCreate());
+ }
+}
diff --git a/integration-tests/srv/src/test/java/com/sap/cds/feature/attachments/integrationtests/testhandler/TestPluginAttachmentsServiceHandlerTest.java b/integration-tests/srv/src/test/java/com/sap/cds/feature/attachments/integrationtests/testhandler/TestPluginAttachmentsServiceHandlerTest.java
index 7428f2ec2..bc391fe9b 100644
--- a/integration-tests/srv/src/test/java/com/sap/cds/feature/attachments/integrationtests/testhandler/TestPluginAttachmentsServiceHandlerTest.java
+++ b/integration-tests/srv/src/test/java/com/sap/cds/feature/attachments/integrationtests/testhandler/TestPluginAttachmentsServiceHandlerTest.java
@@ -5,9 +5,12 @@
import static org.assertj.core.api.Assertions.assertThat;
import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.mockito.Mockito.*;
import com.sap.cds.feature.attachments.generated.cds4j.sap.attachments.MediaData;
+import com.sap.cds.feature.attachments.generated.cds4j.sap.attachments.StatusCode;
+import com.sap.cds.feature.attachments.service.AttachmentService;
import com.sap.cds.feature.attachments.service.model.servicehandler.AttachmentCreateEventContext;
import com.sap.cds.feature.attachments.service.model.servicehandler.AttachmentMarkAsDeletedEventContext;
import com.sap.cds.feature.attachments.service.model.servicehandler.AttachmentReadEventContext;
@@ -17,6 +20,7 @@
import java.io.InputStream;
import java.nio.charset.StandardCharsets;
import java.time.Instant;
+import java.util.List;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
@@ -27,6 +31,9 @@ class TestPluginAttachmentsServiceHandlerTest {
@BeforeEach
void setup() {
cut = new TestPluginAttachmentsServiceHandler();
+ // Clear any previous test data
+ cut.clearEventContext();
+ cut.clearDocuments();
}
@Test
@@ -85,4 +92,175 @@ void dummyTestForRestore() {
assertDoesNotThrow(() -> cut.restoreAttachment(context));
}
+
+ @Test
+ void testCreateAttachmentSetsContentIdAndStatus() throws IOException {
+ var context = AttachmentCreateEventContext.create();
+ context.setData(MediaData.create());
+ context
+ .getData()
+ .setContent(new ByteArrayInputStream("test content".getBytes(StandardCharsets.UTF_8)));
+
+ cut.createAttachment(context);
+
+ assertNotNull(context.getContentId());
+ assertThat(context.getData().getStatus()).isEqualTo(StatusCode.CLEAN);
+ }
+
+ @Test
+ void testEventContextTracking() throws IOException {
+ // Test create event tracking
+ var createContext = AttachmentCreateEventContext.create();
+ createContext.setData(MediaData.create());
+ createContext
+ .getData()
+ .setContent(new ByteArrayInputStream("test".getBytes(StandardCharsets.UTF_8)));
+ cut.createAttachment(createContext);
+
+ List createEvents =
+ cut.getEventContextForEvent(AttachmentService.EVENT_CREATE_ATTACHMENT);
+ assertThat(createEvents).hasSize(1);
+ assertThat(createEvents.get(0).event()).isEqualTo(AttachmentService.EVENT_CREATE_ATTACHMENT);
+
+ // Test read event tracking
+ var readContext = AttachmentReadEventContext.create();
+ readContext.setContentId("test-id");
+ readContext.setData(MediaData.create());
+ cut.readAttachment(readContext);
+
+ List readEvents =
+ cut.getEventContextForEvent(AttachmentService.EVENT_READ_ATTACHMENT);
+ assertThat(readEvents).hasSize(1);
+ assertThat(readEvents.get(0).event()).isEqualTo(AttachmentService.EVENT_READ_ATTACHMENT);
+
+ // Test delete event tracking
+ var deleteContext = AttachmentMarkAsDeletedEventContext.create();
+ deleteContext.setContentId("test-id");
+ cut.markAttachmentAsDeleted(deleteContext);
+
+ List deleteEvents =
+ cut.getEventContextForEvent(AttachmentService.EVENT_MARK_ATTACHMENT_AS_DELETED);
+ assertThat(deleteEvents).hasSize(1);
+ assertThat(deleteEvents.get(0).event())
+ .isEqualTo(AttachmentService.EVENT_MARK_ATTACHMENT_AS_DELETED);
+
+ // Test restore event tracking
+ var restoreContext = AttachmentRestoreEventContext.create();
+ restoreContext.setRestoreTimestamp(Instant.now());
+ cut.restoreAttachment(restoreContext);
+
+ List restoreEvents =
+ cut.getEventContextForEvent(AttachmentService.EVENT_RESTORE_ATTACHMENT);
+ assertThat(restoreEvents).hasSize(1);
+ assertThat(restoreEvents.get(0).event()).isEqualTo(AttachmentService.EVENT_RESTORE_ATTACHMENT);
+ }
+
+ @Test
+ void testGetAllEventContext() throws IOException {
+ // Create multiple events
+ var createContext = AttachmentCreateEventContext.create();
+ createContext.setData(MediaData.create());
+ createContext
+ .getData()
+ .setContent(new ByteArrayInputStream("test".getBytes(StandardCharsets.UTF_8)));
+ cut.createAttachment(createContext);
+
+ var readContext = AttachmentReadEventContext.create();
+ readContext.setContentId("test-id");
+ readContext.setData(MediaData.create());
+ cut.readAttachment(readContext);
+
+ List allEvents = cut.getEventContext();
+ assertThat(allEvents).hasSize(2);
+ }
+
+ @Test
+ void testClearEventContext() throws IOException {
+ // Add some events
+ var context = AttachmentCreateEventContext.create();
+ context.setData(MediaData.create());
+ context.getData().setContent(new ByteArrayInputStream("test".getBytes(StandardCharsets.UTF_8)));
+ cut.createAttachment(context);
+
+ assertThat(cut.getEventContext()).hasSize(1);
+
+ // Clear and verify
+ cut.clearEventContext();
+ assertThat(cut.getEventContext()).isEmpty();
+ }
+
+ @Test
+ void testReadWithNullContentId() {
+ var context = AttachmentReadEventContext.create();
+ context.setContentId(null);
+ context.setData(MediaData.create());
+
+ cut.readAttachment(context);
+
+ assertThat(context.getData().getContent()).isNull();
+ }
+
+ @Test
+ void testCreateAttachmentWithEmptyContent() throws IOException {
+ var context = AttachmentCreateEventContext.create();
+ context.setData(MediaData.create());
+ context.getData().setContent(new ByteArrayInputStream(new byte[0]));
+
+ cut.createAttachment(context);
+
+ assertNotNull(context.getContentId());
+ assertThat(context.getData().getStatus()).isEqualTo(StatusCode.CLEAN);
+ }
+
+ @Test
+ void testMultipleCreateAndReadOperations() throws IOException {
+ // Create first attachment
+ var createContext1 = AttachmentCreateEventContext.create();
+ createContext1.setData(MediaData.create());
+ createContext1
+ .getData()
+ .setContent(new ByteArrayInputStream("content1".getBytes(StandardCharsets.UTF_8)));
+ cut.createAttachment(createContext1);
+
+ // Create second attachment
+ var createContext2 = AttachmentCreateEventContext.create();
+ createContext2.setData(MediaData.create());
+ createContext2
+ .getData()
+ .setContent(new ByteArrayInputStream("content2".getBytes(StandardCharsets.UTF_8)));
+ cut.createAttachment(createContext2);
+
+ // Read first attachment
+ var readContext1 = AttachmentReadEventContext.create();
+ readContext1.setContentId(createContext1.getContentId());
+ readContext1.setData(MediaData.create());
+ cut.readAttachment(readContext1);
+
+ // Read second attachment
+ var readContext2 = AttachmentReadEventContext.create();
+ readContext2.setContentId(createContext2.getContentId());
+ readContext2.setData(MediaData.create());
+ cut.readAttachment(readContext2);
+
+ // Verify content
+ assertThat(readContext1.getData().getContent().readAllBytes())
+ .isEqualTo("content1".getBytes(StandardCharsets.UTF_8));
+ assertThat(readContext2.getData().getContent().readAllBytes())
+ .isEqualTo("content2".getBytes(StandardCharsets.UTF_8));
+ }
+
+ @Test
+ void testRestoreWithSpecificTimestamp() {
+ Instant timestamp = Instant.parse("2024-01-01T12:00:00Z");
+ var context = AttachmentRestoreEventContext.create();
+ context.setRestoreTimestamp(timestamp);
+
+ cut.restoreAttachment(context);
+
+ List restoreEvents =
+ cut.getEventContextForEvent(AttachmentService.EVENT_RESTORE_ATTACHMENT);
+ assertThat(restoreEvents).hasSize(1);
+ var restoredContext = (AttachmentRestoreEventContext) restoreEvents.get(0).context();
+ assertThat(restoredContext.getRestoreTimestamp()).isEqualTo(timestamp);
+ }
}
diff --git a/samples/bookshop/.cdsrc.json b/samples/bookshop/.cdsrc.json
new file mode 100644
index 000000000..2c63c0851
--- /dev/null
+++ b/samples/bookshop/.cdsrc.json
@@ -0,0 +1,2 @@
+{
+}
diff --git a/samples/bookshop/.gitignore b/samples/bookshop/.gitignore
new file mode 100644
index 000000000..c161f228e
--- /dev/null
+++ b/samples/bookshop/.gitignore
@@ -0,0 +1,31 @@
+**/gen/
+**/edmx/
+*.db
+*.sqlite
+*.sqlite-wal
+*.sqlite-shm
+schema*.sql
+default-env.json
+
+**/bin/
+**/target/
+.flattened-pom.xml
+.classpath
+.project
+.settings
+
+**/node/
+**/node_modules/
+
+**/.mta/
+*.mtar
+
+*.log*
+gc_history*
+hs_err*
+*.tgz
+*.iml
+
+.vscode
+.idea
+.reloadtrigger
diff --git a/samples/bookshop/README.md b/samples/bookshop/README.md
new file mode 100644
index 000000000..b071a4644
--- /dev/null
+++ b/samples/bookshop/README.md
@@ -0,0 +1,128 @@
+# Bookshop Sample - Attachments Plugin
+
+This sample demonstrates how to use the `cds-feature-attachments` plugin in a CAP Java application. It extends the classic CAP bookshop sample to include file attachments for books.
+
+## What This Sample Demonstrates
+
+- Integration of the latest attachments plugin with CAP Java
+- Extending existing entities with attachment capabilities
+- UI integration with Fiori elements applications
+- Basic attachment operations (upload, download, delete)
+
+## Prerequisites
+
+- Java 17 or higher
+- Maven 3.6 or higher
+- Node.js 18 or higher
+- npm
+
+## Getting Started
+
+1. **Clone and navigate to the sample**:
+ ```bash
+ cd samples/bookshop
+ ```
+
+2. **Install dependencies**:
+ ```bash
+ npm install
+ mvn clean install
+ ```
+
+3. **Run the application**:
+ ```bash
+ mvn spring-boot:run
+ ```
+
+4. **Access the application**:
+ - Browse Books: http://localhost:8080/browse/index.html
+ - Admin Books: http://localhost:8080/admin-books/index.html
+
+## Using Attachments
+
+Once the application is running:
+
+1. Navigate to the Books app (browse or admin)
+2. Select any book to open its details
+3. Scroll down to find the "Attachments" section
+4. Use the attachment controls to:
+ - Upload files by clicking the upload button
+ - View uploaded files in the attachment list
+ - Download files by clicking on them
+ - Delete files using the delete button
+
+## Implementation Details
+
+### Maven Configuration
+
+The attachments plugin is added to `srv/pom.xml`:
+
+```xml
+
+ com.sap.cds
+ cds-feature-attachments
+
+```
+
+The `cds-maven-plugin` includes the `resolve` goal to make CDS models from dependencies available:
+
+```xml
+
+ com.sap.cds
+ cds-maven-plugin
+
+
+ cds.resolve
+
+ resolve
+
+
+
+
+
+```
+
+### CDS Model Extension
+
+The `srv/attachments.cds` file extends the Books entity with attachments:
+
+```cds
+using { sap.capire.bookshop as my } from '../db/schema';
+using { sap.attachments.Attachments } from 'com.sap.cds/cds-feature-attachments';
+
+extend my.Books with {
+ attachments: Composition of many Attachments;
+}
+```
+
+### UI Integration
+
+The same file adds UI facets for both services to display attachments in the Fiori apps:
+
+```cds
+using { CatalogService as service } from '../app/services';
+annotate service.Books with @(
+ UI.Facets: [
+ {
+ $Type : 'UI.ReferenceFacet',
+ ID : 'AttachmentsFacet',
+ Label : '{i18n>attachments}',
+ Target : 'attachments/@UI.LineItem'
+ }
+ ]
+);
+```
+
+## Storage Configuration
+
+This sample uses the default in-memory storage, which stores attachments directly in the H2 database. For production scenarios, consider using object store backends.
+
+## Advanced Configuration
+
+For advanced topics like object store integration, malware scanning, and security configuration, see the [main project documentation](../../README.md).
+
+## Troubleshooting
+
+- **Port conflicts**: If port 8080 is in use, specify a different port: `mvn spring-boot:run -Dserver.port=8081`
+- **Memory issues**: Increase JVM heap size: `export MAVEN_OPTS="-Xmx2g"`
+- **File upload issues**: Check browser developer console for error messages
diff --git a/samples/bookshop/app/_i18n/i18n.properties b/samples/bookshop/app/_i18n/i18n.properties
new file mode 100644
index 000000000..7326bbb72
--- /dev/null
+++ b/samples/bookshop/app/_i18n/i18n.properties
@@ -0,0 +1,15 @@
+Books = Books
+Book = Book
+ID = ID
+Title = Title
+Author = Author
+Authors = Authors
+AuthorID = Author ID
+AuthorName = Author Name
+Name = Name
+Age = Age
+Stock = Stock
+Order = Order
+Orders = Orders
+Price = Price
+Genre = Genre
\ No newline at end of file
diff --git a/samples/bookshop/app/_i18n/i18n_de.properties b/samples/bookshop/app/_i18n/i18n_de.properties
new file mode 100644
index 000000000..cb712c12c
--- /dev/null
+++ b/samples/bookshop/app/_i18n/i18n_de.properties
@@ -0,0 +1,15 @@
+Books = Bücher
+Book = Buch
+ID = ID
+Title = Titel
+Author = Autor
+Authors = Autoren
+AuthorID = ID des Autors
+AuthorName = Name des Autors
+Name = Name
+Age = Alter
+Stock = Bestand
+Order = Bestellung
+Orders = Bestellungen
+Price = Preis
+Genre = Genre
\ No newline at end of file
diff --git a/samples/bookshop/app/admin-books/fiori-service.cds b/samples/bookshop/app/admin-books/fiori-service.cds
new file mode 100644
index 000000000..36fa09086
--- /dev/null
+++ b/samples/bookshop/app/admin-books/fiori-service.cds
@@ -0,0 +1,113 @@
+using {AdminService} from '../../srv/admin-service.cds';
+
+////////////////////////////////////////////////////////////////////////////
+//
+// Books Object Page
+//
+annotate AdminService.Books with @(UI: {
+ HeaderInfo : {
+ TypeName : '{i18n>Book}',
+ TypeNamePlural: '{i18n>Books}',
+ Title : {Value: title},
+ Description : {Value: author.name}
+ },
+ Facets : [
+ {
+ $Type : 'UI.ReferenceFacet',
+ Label : '{i18n>General}',
+ Target: '@UI.FieldGroup#General'
+ },
+ {
+ $Type : 'UI.ReferenceFacet',
+ Label : '{i18n>Translations}',
+ Target: 'texts/@UI.LineItem'
+ },
+ {
+ $Type : 'UI.ReferenceFacet',
+ Label : '{i18n>Details}',
+ Target: '@UI.FieldGroup#Details'
+ },
+ {
+ $Type : 'UI.ReferenceFacet',
+ Label : '{i18n>Admin}',
+ Target: '@UI.FieldGroup#Admin'
+ }
+ ],
+ FieldGroup #General: {Data: [
+ {Value: title},
+ {Value: author_ID},
+ {Value: genre_ID},
+ {Value: descr}
+ ]},
+ FieldGroup #Details: {Data: [
+ {Value: stock},
+ {Value: price}
+ ]},
+ FieldGroup #Admin : {Data: [
+ {Value: createdBy},
+ {Value: createdAt},
+ {Value: modifiedBy},
+ {Value: modifiedAt}
+ ]}
+});
+
+
+////////////////////////////////////////////////////////////
+//
+// Draft for Localized Data
+//
+annotate sap.capire.bookshop.Books with @fiori.draft.enabled;
+annotate AdminService.Books with @odata.draft.enabled;
+
+annotate AdminService.Books.texts with @(UI: {
+ Identification : [{Value: title}],
+ SelectionFields: [
+ locale,
+ title
+ ],
+ LineItem : [
+ {
+ Value: locale,
+ Label: 'Locale'
+ },
+ {
+ Value: title,
+ Label: 'Title'
+ },
+ {
+ Value: descr,
+ Label: 'Description'
+ }
+ ]
+});
+
+annotate AdminService.Books.texts with {
+ ID @UI.Hidden;
+ ID_texts @UI.Hidden;
+};
+
+// Add Value Help for Locales
+annotate AdminService.Books.texts {
+ locale @(
+ ValueList.entity: 'Languages',
+ Common.ValueListWithFixedValues //show as drop down, not a dialog
+ )
+};
+
+// In addition we need to expose Languages through AdminService as a target for ValueList
+using {sap} from '@sap/cds/common';
+
+extend service AdminService {
+ @readonly
+ entity Languages as projection on sap.common.Languages;
+}
+
+// Workaround for Fiori popup for asking user to enter a new UUID on Create
+annotate AdminService.Books with {
+ ID @Core.Computed;
+}
+
+// Show Genre as drop down, not a dialog
+annotate AdminService.Books with {
+ genre @Common.ValueListWithFixedValues;
+}
diff --git a/samples/bookshop/app/admin-books/webapp/Component.js b/samples/bookshop/app/admin-books/webapp/Component.js
new file mode 100644
index 000000000..e98677ee9
--- /dev/null
+++ b/samples/bookshop/app/admin-books/webapp/Component.js
@@ -0,0 +1,8 @@
+sap.ui.define(["sap/fe/core/AppComponent"], function (AppComponent) {
+ "use strict";
+ return AppComponent.extend("books.Component", {
+ metadata: { manifest: "json" }
+ });
+});
+
+/* eslint no-undef:0 */
diff --git a/samples/bookshop/app/admin-books/webapp/i18n/i18n.properties b/samples/bookshop/app/admin-books/webapp/i18n/i18n.properties
new file mode 100644
index 000000000..9a23ee40a
--- /dev/null
+++ b/samples/bookshop/app/admin-books/webapp/i18n/i18n.properties
@@ -0,0 +1,3 @@
+appTitle=Manage Books
+appSubTitle=Manage bookshop inventory
+appDescription=Manage your bookshop inventory with ease.
diff --git a/samples/bookshop/app/admin-books/webapp/i18n/i18n_de.properties b/samples/bookshop/app/admin-books/webapp/i18n/i18n_de.properties
new file mode 100644
index 000000000..01d56a22c
--- /dev/null
+++ b/samples/bookshop/app/admin-books/webapp/i18n/i18n_de.properties
@@ -0,0 +1,3 @@
+appTitle=Bücher verwalten
+appSubTitle=Verwalten Sie den Bestand der Buchhandlungen
+appDescription=Verwalten Sie den Bestand Ihrer Buchhandlung ganz einfach.
diff --git a/samples/bookshop/app/admin-books/webapp/manifest.json b/samples/bookshop/app/admin-books/webapp/manifest.json
new file mode 100644
index 000000000..4bcc54ceb
--- /dev/null
+++ b/samples/bookshop/app/admin-books/webapp/manifest.json
@@ -0,0 +1,145 @@
+{
+ "_version": "1.49.0",
+ "sap.app": {
+ "applicationVersion": {
+ "version": "1.0.0"
+ },
+ "id": "bookshop.admin-books",
+ "type": "application",
+ "title": "{{appTitle}}",
+ "description": "{{appDescription}}",
+ "i18n": "i18n/i18n.properties",
+ "dataSources": {
+ "AdminService": {
+ "uri": "/odata/v4/AdminService/",
+ "type": "OData",
+ "settings": {
+ "odataVersion": "4.0"
+ }
+ }
+ },
+ "crossNavigation": {
+ "inbounds": {
+ "intent-Books-manage": {
+ "signature": {
+ "parameters": {},
+ "additionalParameters": "allowed"
+ },
+ "semanticObject": "Books",
+ "action": "manage"
+ }
+ }
+ }
+ },
+ "sap.ui": {
+ "technology": "UI5",
+ "fullWidth": false,
+ "deviceTypes": {
+ "desktop": true,
+ "tablet": true,
+ "phone": true
+ }
+ },
+ "sap.ui5": {
+ "dependencies": {
+ "minUI5Version": "1.115.1",
+ "libs": {
+ "sap.fe.templates": {}
+ }
+ },
+ "models": {
+ "i18n": {
+ "type": "sap.ui.model.resource.ResourceModel",
+ "uri": "i18n/i18n.properties"
+ },
+ "": {
+ "dataSource": "AdminService",
+ "settings": {
+ "operationMode": "Server",
+ "autoExpandSelect": true,
+ "earlyRequests": true,
+ "groupProperties": {
+ "default": {
+ "submit": "Auto"
+ }
+ }
+ }
+ }
+ },
+ "routing": {
+ "routes": [
+ {
+ "pattern": ":?query:",
+ "name": "BooksList",
+ "target": "BooksList"
+ },
+ {
+ "pattern": "Books({key}):?query:",
+ "name": "BooksDetails",
+ "target": "BooksDetails"
+ },
+ {
+ "pattern": "Books({key}/author({key2}):?query:",
+ "name": "AuthorsDetails",
+ "target": "AuthorsDetails"
+ }
+ ],
+ "targets": {
+ "BooksList": {
+ "type": "Component",
+ "id": "BooksList",
+ "name": "sap.fe.templates.ListReport",
+ "options": {
+ "settings": {
+ "contextPath": "/Books",
+ "initialLoad": true,
+ "navigation": {
+ "Books": {
+ "detail": {
+ "route": "BooksDetails"
+ }
+ }
+ }
+ }
+ }
+ },
+ "BooksDetails": {
+ "type": "Component",
+ "id": "BooksDetailsList",
+ "name": "sap.fe.templates.ObjectPage",
+ "options": {
+ "settings": {
+ "contextPath": "/Books",
+ "editableHeaderContent": false,
+ "navigation": {
+ "Authors": {
+ "detail": {
+ "route": "AuthorsDetails"
+ }
+ }
+ }
+ }
+ }
+ },
+ "AuthorsDetails": {
+ "type": "Component",
+ "id": "AuthorsDetailsList",
+ "name": "sap.fe.templates.ObjectPage",
+ "options": {
+ "settings": {
+ "contextPath": "/Authors"
+ }
+ }
+ }
+ }
+ },
+ "contentDensities": {
+ "compact": true,
+ "cozy": true
+ }
+ },
+ "sap.fiori": {
+ "registrationIds": [],
+ "archeType": "transactional"
+ }
+}
diff --git a/samples/bookshop/app/appconfig/fioriSandboxConfig.json b/samples/bookshop/app/appconfig/fioriSandboxConfig.json
new file mode 100644
index 000000000..ff2ac499b
--- /dev/null
+++ b/samples/bookshop/app/appconfig/fioriSandboxConfig.json
@@ -0,0 +1,95 @@
+{
+ "services": {
+ "LaunchPage": {
+ "adapter": {
+ "config": {
+ "catalogs": [],
+ "groups": [
+ {
+ "id": "Bookshop",
+ "title": "Bookshop",
+ "isPreset": true,
+ "isVisible": true,
+ "isGroupLocked": false,
+ "tiles": [
+ {
+ "id": "BrowseBooks",
+ "tileType": "sap.ushell.ui.tile.StaticTile",
+ "properties": {
+ "title": "Browse Books",
+ "targetURL": "#Books-display"
+ }
+ }
+ ]
+ },
+ {
+ "id": "Administration",
+ "title": "Administration",
+ "isPreset": true,
+ "isVisible": true,
+ "isGroupLocked": false,
+ "tiles": [
+ {
+ "id": "ManageBooks",
+ "tileType": "sap.ushell.ui.tile.StaticTile",
+ "properties": {
+ "title": "Manage Books",
+ "targetURL": "#Books-manage"
+ }
+ }
+ ]
+ }
+ ]
+ }
+ }
+ },
+ "NavTargetResolution": {
+ "config": {
+ "enableClientSideTargetResolution": true
+ }
+ },
+ "ClientSideTargetResolution": {
+ "adapter": {
+ "config": {
+ "inbounds": {
+ "BrowseBooks": {
+ "semanticObject": "Books",
+ "action": "display",
+ "title": "Browse Books",
+ "signature": {
+ "parameters": {
+ "Books.ID": {
+ "renameTo": "ID"
+ },
+ "Authors.books.ID": {
+ "renameTo": "ID"
+ }
+ },
+ "additionalParameters": "ignored"
+ },
+ "resolutionResult": {
+ "applicationType": "SAPUI5",
+ "additionalInformation": "SAPUI5.Component=bookshop",
+ "url": "browse/webapp"
+ }
+ },
+ "ManageBooks": {
+ "semanticObject": "Books",
+ "action": "manage",
+ "title": "Manage Books",
+ "signature": {
+ "parameters": {},
+ "additionalParameters": "allowed"
+ },
+ "resolutionResult": {
+ "applicationType": "SAPUI5",
+ "additionalInformation": "SAPUI5.Component=books",
+ "url": "admin-books/webapp"
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/samples/bookshop/app/browse/fiori-service.cds b/samples/bookshop/app/browse/fiori-service.cds
new file mode 100644
index 000000000..b49a94f18
--- /dev/null
+++ b/samples/bookshop/app/browse/fiori-service.cds
@@ -0,0 +1,51 @@
+using {CatalogService} from '../../srv/cat-service.cds';
+
+////////////////////////////////////////////////////////////////////////////
+//
+// Books Object Page
+//
+annotate CatalogService.Books with @(UI: {
+ HeaderInfo : {
+ TypeName : '{i18n>Book}',
+ TypeNamePlural: '{i18n>Books}',
+ Title : {Value: title},
+ Description : {Value: author}
+ },
+ HeaderFacets : [{
+ $Type : 'UI.ReferenceFacet',
+ Label : '{i18n>Description}',
+ Target: '@UI.FieldGroup#Descr'
+ }],
+ Facets : [{
+ $Type : 'UI.ReferenceFacet',
+ Label : '{i18n>Details}',
+ Target: '@UI.FieldGroup#Price'
+ }],
+ FieldGroup #Descr: {Data: [{Value: descr}]},
+ FieldGroup #Price: {Data: [{Value: price}]}
+});
+
+////////////////////////////////////////////////////////////////////////////
+//
+// Books List Page
+//
+annotate CatalogService.Books with @(UI: {
+ SelectionFields: [
+ ID,
+ price,
+ currency_code
+ ],
+ LineItem : [
+ {
+ Value: ID,
+ Label: '{i18n>Title}'
+ },
+ {
+ Value: author,
+ Label: '{i18n>Author}'
+ },
+ {Value: genre.name},
+ {Value: price},
+ {Value: currency.symbol}
+ ]
+});
diff --git a/samples/bookshop/app/browse/webapp/Component.js b/samples/bookshop/app/browse/webapp/Component.js
new file mode 100644
index 000000000..4020679f8
--- /dev/null
+++ b/samples/bookshop/app/browse/webapp/Component.js
@@ -0,0 +1,7 @@
+sap.ui.define(["sap/fe/core/AppComponent"], function(AppComponent) {
+ "use strict";
+ return AppComponent.extend("bookshop.Component", {
+ metadata: { manifest: "json" }
+ });
+});
+/* eslint no-undef:0 */
diff --git a/samples/bookshop/app/browse/webapp/i18n/i18n.properties b/samples/bookshop/app/browse/webapp/i18n/i18n.properties
new file mode 100644
index 000000000..21436e8e4
--- /dev/null
+++ b/samples/bookshop/app/browse/webapp/i18n/i18n.properties
@@ -0,0 +1,3 @@
+appTitle=Browse Books
+appSubTitle=Find all your favorite books
+appDescription=This application lets you find the next books you want to read.
diff --git a/samples/bookshop/app/browse/webapp/i18n/i18n_de.properties b/samples/bookshop/app/browse/webapp/i18n/i18n_de.properties
new file mode 100644
index 000000000..ea86c3f29
--- /dev/null
+++ b/samples/bookshop/app/browse/webapp/i18n/i18n_de.properties
@@ -0,0 +1,3 @@
+appTitle=Bücher anschauen
+appSubTitle=Finden sie ihre nächste Lektüre
+appDescription=Finden Sie die nachsten Bücher, die Sie lesen möchten.
diff --git a/samples/bookshop/app/browse/webapp/manifest.json b/samples/bookshop/app/browse/webapp/manifest.json
new file mode 100644
index 000000000..cd4b1c3cf
--- /dev/null
+++ b/samples/bookshop/app/browse/webapp/manifest.json
@@ -0,0 +1,137 @@
+{
+ "_version": "1.49.0",
+ "sap.app": {
+ "id": "bookshop.browse",
+ "applicationVersion": {
+ "version": "1.0.0"
+ },
+ "type": "application",
+ "title": "{{appTitle}}",
+ "description": "{{appDescription}}",
+ "i18n": "i18n/i18n.properties",
+ "dataSources": {
+ "CatalogService": {
+ "uri": "/odata/v4/CatalogService/",
+ "type": "OData",
+ "settings": {
+ "odataVersion": "4.0"
+ }
+ }
+ },
+ "crossNavigation": {
+ "inbounds": {
+ "intent1": {
+ "signature": {
+ "parameters": {
+ "Books.ID": {
+ "renameTo": "ID"
+ },
+ "Authors.books.ID": {
+ "renameTo": "ID"
+ }
+ },
+ "additionalParameters": "ignored"
+ },
+ "semanticObject": "Books",
+ "action": "display",
+ "title": "{{appTitle}}",
+ "subTitle": "{{appSubTitle}}",
+ "icon": "sap-icon://course-book",
+ "indicatorDataSource": {
+ "dataSource": "CatalogService",
+ "path": "Books/$count",
+ "refresh": 1800
+ }
+ }
+ }
+ }
+ },
+ "sap.ui": {
+ "technology": "UI5",
+ "fullWidth": false,
+ "deviceTypes": {
+ "desktop": true,
+ "tablet": true,
+ "phone": true
+ }
+ },
+ "sap.ui5": {
+ "dependencies": {
+ "minUI5Version": "1.115.1",
+ "libs": {
+ "sap.fe.templates": {}
+ }
+ },
+ "models": {
+ "i18n": {
+ "type": "sap.ui.model.resource.ResourceModel",
+ "uri": "i18n/i18n.properties"
+ },
+ "": {
+ "dataSource": "CatalogService",
+ "settings": {
+ "operationMode": "Server",
+ "autoExpandSelect": true,
+ "earlyRequests": true,
+ "groupProperties": {
+ "default": {
+ "submit": "Auto"
+ }
+ }
+ }
+ }
+ },
+ "routing": {
+ "routes": [
+ {
+ "pattern": ":?query:",
+ "name": "BooksList",
+ "target": "BooksList"
+ },
+ {
+ "pattern": "Books({key}):?query:",
+ "name": "BooksDetails",
+ "target": "BooksDetails"
+ }
+ ],
+ "targets": {
+ "BooksList": {
+ "type": "Component",
+ "id": "BooksList",
+ "name": "sap.fe.templates.ListReport",
+ "options": {
+ "settings": {
+ "contextPath": "/Books",
+ "initialLoad": true,
+ "navigation": {
+ "Books": {
+ "detail": {
+ "route": "BooksDetails"
+ }
+ }
+ }
+ }
+ }
+ },
+ "BooksDetails": {
+ "type": "Component",
+ "id": "BooksDetailsList",
+ "name": "sap.fe.templates.ObjectPage",
+ "options": {
+ "settings": {
+ "contextPath": "/Books"
+ }
+ }
+ }
+ }
+ },
+ "contentDensities": {
+ "compact": true,
+ "cozy": true
+ }
+ },
+ "sap.fiori": {
+ "registrationIds": [],
+ "archeType": "transactional"
+ }
+}
diff --git a/samples/bookshop/app/common.cds b/samples/bookshop/app/common.cds
new file mode 100644
index 000000000..69627beb5
--- /dev/null
+++ b/samples/bookshop/app/common.cds
@@ -0,0 +1,264 @@
+/*
+ Common Annotations shared by all apps
+*/
+
+using {sap.capire.bookshop as my} from '../db/schema';
+using {
+ sap.common,
+ sap.common.Currencies
+} from '@sap/cds/common';
+
+////////////////////////////////////////////////////////////////////////////
+//
+// Books Lists
+//
+annotate my.Books with @(
+ Common.SemanticKey: [ID],
+ UI : {
+ Identification : [{Value: title}],
+ SelectionFields: [
+ ID,
+ author_ID,
+ price,
+ currency_code
+ ],
+ LineItem : [
+ {
+ Value: ID,
+ Label: '{i18n>Title}'
+ },
+ {
+ Value: author.ID,
+ Label: '{i18n>Author}'
+ },
+ {Value: genre.name},
+ {Value: stock},
+ {Value: price},
+ {Value: currency.symbol}
+ ]
+ }
+) {
+ ID @Common : {
+ SemanticObject : 'Books',
+ Text : title,
+ TextArrangement: #TextOnly
+ };
+ author @ValueList.entity: 'Authors';
+};
+
+annotate Currencies with {
+ symbol @Common.Label: '{i18n>Currency}';
+}
+
+////////////////////////////////////////////////////////////////////////////
+//
+// Books Elements
+//
+annotate my.Books with {
+ ID @title: '{i18n>ID}';
+ title @title: '{i18n>Title}';
+ genre @title: '{i18n>Genre}' @Common : {
+ Text : genre.name,
+ TextArrangement: #TextOnly
+ };
+ author @title: '{i18n>Author}' @Common : {
+ Text : author.name,
+ TextArrangement: #TextOnly
+ };
+ price @title: '{i18n>Price}' @Measures.ISOCurrency: currency_code;
+ stock @title: '{i18n>Stock}';
+ descr @title: '{i18n>Description}' @UI.MultiLineText;
+ image @title: '{i18n>Image}';
+}
+
+////////////////////////////////////////////////////////////////////////////
+//
+// Genres List
+//
+annotate my.Genres with @(
+ Common.SemanticKey: [name],
+ UI : {
+ SelectionFields: [name],
+ LineItem : [
+ {Value: name},
+ {
+ Value: parent.name,
+ Label: 'Main Genre'
+ }
+ ]
+ }
+);
+
+annotate my.Genres with {
+ ID @Common.Text: name @Common.TextArrangement: #TextOnly;
+}
+
+////////////////////////////////////////////////////////////////////////////
+//
+// Genre Details
+//
+annotate my.Genres with @(UI: {
+ Identification: [{Value: name}],
+ HeaderInfo : {
+ TypeName : '{i18n>Genre}',
+ TypeNamePlural: '{i18n>Genres}',
+ Title : {Value: name},
+ Description : {Value: ID}
+ },
+ Facets : [{
+ $Type : 'UI.ReferenceFacet',
+ Label : '{i18n>SubGenres}',
+ Target: 'children/@UI.LineItem'
+ }]
+});
+
+////////////////////////////////////////////////////////////////////////////
+//
+// Genres Elements
+//
+annotate my.Genres with {
+ ID @title: '{i18n>ID}';
+ name @title: '{i18n>Genre}';
+}
+
+////////////////////////////////////////////////////////////////////////////
+//
+// Authors List
+//
+annotate my.Authors with @(
+ Common.SemanticKey: [ID],
+ UI : {
+ Identification : [{Value: name}],
+ SelectionFields: [name],
+ LineItem : [
+ {Value: ID},
+ {Value: dateOfBirth},
+ {Value: dateOfDeath},
+ {Value: placeOfBirth},
+ {Value: placeOfDeath}
+ ]
+ }
+) {
+ ID @Common: {
+ SemanticObject : 'Authors',
+ Text : name,
+ TextArrangement: #TextOnly
+ };
+};
+
+////////////////////////////////////////////////////////////////////////////
+//
+// Author Details
+//
+annotate my.Authors with @(UI: {
+ HeaderInfo: {
+ TypeName : '{i18n>Author}',
+ TypeNamePlural: '{i18n>Authors}',
+ Title : {Value: name},
+ Description : {Value: dateOfBirth}
+ },
+ Facets : [{
+ $Type : 'UI.ReferenceFacet',
+ Target: 'books/@UI.LineItem'
+ }]
+});
+
+
+////////////////////////////////////////////////////////////////////////////
+//
+// Authors Elements
+//
+annotate my.Authors with {
+ ID @title: '{i18n>ID}';
+ name @title: '{i18n>Name}';
+ dateOfBirth @title: '{i18n>DateOfBirth}';
+ dateOfDeath @title: '{i18n>DateOfDeath}';
+ placeOfBirth @title: '{i18n>PlaceOfBirth}';
+ placeOfDeath @title: '{i18n>PlaceOfDeath}';
+}
+
+////////////////////////////////////////////////////////////////////////////
+//
+// Languages List
+//
+annotate common.Languages with @(
+ Common.SemanticKey: [code],
+ Identification : [{Value: code}],
+ UI : {
+ SelectionFields: [
+ name,
+ descr
+ ],
+ LineItem : [
+ {Value: code},
+ {Value: name}
+ ]
+ }
+);
+
+////////////////////////////////////////////////////////////////////////////
+//
+// Language Details
+//
+annotate common.Languages with @(UI: {
+ HeaderInfo : {
+ TypeName : '{i18n>Language}',
+ TypeNamePlural: '{i18n>Languages}',
+ Title : {Value: name},
+ Description : {Value: descr}
+ },
+ Facets : [{
+ $Type : 'UI.ReferenceFacet',
+ Label : '{i18n>Details}',
+ Target: '@UI.FieldGroup#Details'
+ }],
+ FieldGroup #Details: {Data: [
+ {Value: code},
+ {Value: name},
+ {Value: descr}
+ ]}
+});
+
+////////////////////////////////////////////////////////////////////////////
+//
+// Currencies List
+//
+annotate common.Currencies with @(
+ Common.SemanticKey: [code],
+ Identification : [{Value: code}],
+ UI : {
+ SelectionFields: [
+ name,
+ descr
+ ],
+ LineItem : [
+ {Value: descr},
+ {Value: symbol},
+ {Value: code}
+ ]
+ }
+);
+
+////////////////////////////////////////////////////////////////////////////
+//
+// Currency Details
+//
+annotate common.Currencies with @(UI: {
+ HeaderInfo : {
+ TypeName : '{i18n>Currency}',
+ TypeNamePlural: '{i18n>Currencies}',
+ Title : {Value: descr},
+ Description : {Value: code}
+ },
+ Facets : [{
+ $Type : 'UI.ReferenceFacet',
+ Label : '{i18n>Details}',
+ Target: '@UI.FieldGroup#Details'
+ }],
+ FieldGroup #Details: {Data: [
+ {Value: name},
+ {Value: symbol},
+ {Value: code},
+ {Value: descr}
+ ]}
+});
diff --git a/samples/bookshop/app/index.html b/samples/bookshop/app/index.html
new file mode 100644
index 000000000..70f631507
--- /dev/null
+++ b/samples/bookshop/app/index.html
@@ -0,0 +1,32 @@
+
+
+
+
+
+
+
+ Bookshop
+
+
+
+
+
+
+
+
+
+
diff --git a/samples/bookshop/app/services.cds b/samples/bookshop/app/services.cds
new file mode 100644
index 000000000..87e7b310f
--- /dev/null
+++ b/samples/bookshop/app/services.cds
@@ -0,0 +1,6 @@
+/*
+ This model controls what gets served to Fiori frontends...
+*/
+using from './common';
+using from './browse/fiori-service';
+using from './admin-books/fiori-service';
diff --git a/samples/bookshop/db/data/sap.capire.bookshop-Authors.csv b/samples/bookshop/db/data/sap.capire.bookshop-Authors.csv
new file mode 100644
index 000000000..5272ee157
--- /dev/null
+++ b/samples/bookshop/db/data/sap.capire.bookshop-Authors.csv
@@ -0,0 +1,5 @@
+ID;name;dateOfBirth;placeOfBirth;dateOfDeath;placeOfDeath
+10fef92e-975f-4c41-8045-c58e5c27a040;Emily Brontë;1818-07-30;Thornton, Yorkshire;1848-12-19;Haworth, Yorkshire
+d4585e0e-ab3b-4424-b2ac-f2bfa785f068;Charlotte Brontë;1818-04-21;Thornton, Yorkshire;1855-03-31;Haworth, Yorkshire
+4cf60975-300d-4dbe-8598-57b02e62bae2;Edgar Allen Poe;1809-01-19;Boston, Massachusetts;1849-10-07;Baltimore, Maryland
+df9fb9fa-f121-45b5-8be5-8ff7ad5219a2;Richard Carpenter;1929-08-14;King’s Lynn, Norfolk;2012-02-26;Hertfordshire, England
diff --git a/samples/bookshop/db/data/sap.capire.bookshop-Books.csv b/samples/bookshop/db/data/sap.capire.bookshop-Books.csv
new file mode 100644
index 000000000..46d63fa5d
--- /dev/null
+++ b/samples/bookshop/db/data/sap.capire.bookshop-Books.csv
@@ -0,0 +1,6 @@
+ID;title;descr;author_ID;stock;price;currency_code;genre_ID
+aeeda49f-72f2-4880-be27-a513b2e53040;Wuthering Heights;"Wuthering Heights, Emily Brontë's only novel, was published in 1847 under the pseudonym ""Ellis Bell"". It was written between October 1845 and June 1846. Wuthering Heights and Anne Brontë's Agnes Grey were accepted by publisher Thomas Newby before the success of their sister Charlotte's novel Jane Eyre. After Emily's death, Charlotte edited the manuscript of Wuthering Heights and arranged for the edited version to be published as a posthumous second edition in 1850.";10fef92e-975f-4c41-8045-c58e5c27a040;12;11.11;GBP;11
+b0056977-4cf5-46a2-ab14-6409ee2e0df1;Jane Eyre;"Jane Eyre /ɛər/ (originally published as Jane Eyre: An Autobiography) is a novel by English writer Charlotte Brontë, published under the pen name ""Currer Bell"", on 16 October 1847, by Smith, Elder & Co. of London. The first American edition was published the following year by Harper & Brothers of New York. Primarily a bildungsroman, Jane Eyre follows the experiences of its eponymous heroine, including her growth to adulthood and her love for Mr. Rochester, the brooding master of Thornfield Hall. The novel revolutionised prose fiction in that the focus on Jane's moral and spiritual development is told through an intimate, first-person narrative, where actions and events are coloured by a psychological intensity. The book contains elements of social criticism, with a strong sense of Christian morality at its core and is considered by many to be ahead of its time because of Jane's individualistic character and how the novel approaches the topics of class, sexuality, religion and feminism.";d4585e0e-ab3b-4424-b2ac-f2bfa785f068;11;12.34;GBP;11
+c7641340-a9be-4673-8dad-785a2505f46e;The Raven;"""The Raven"" is a narrative poem by American writer Edgar Allan Poe. First published in January 1845, the poem is often noted for its musicality, stylized language, and supernatural atmosphere. It tells of a talking raven's mysterious visit to a distraught lover, tracing the man's slow fall into madness. The lover, often identified as being a student, is lamenting the loss of his love, Lenore. Sitting on a bust of Pallas, the raven seems to further distress the protagonist with its constant repetition of the word ""Nevermore"". The poem makes use of folk, mythological, religious, and classical references.";4cf60975-300d-4dbe-8598-57b02e62bae2;333;13.13;USD;16
+7756b725-cefc-43a2-a3c8-0c9104a349b8;Eleonora;"""Eleonora"" is a short story by Edgar Allan Poe, first published in 1842 in Philadelphia in the literary annual The Gift. It is often regarded as somewhat autobiographical and has a relatively ""happy"" ending.";4cf60975-300d-4dbe-8598-57b02e62bae2;555;14;USD;16
+a009c640-434a-4542-ac68-51b400c880ea;Catweazle;Catweazle is a British fantasy television series, starring Geoffrey Bayldon in the title role, and created by Richard Carpenter for London Weekend Television. The first series, produced and directed by Quentin Lawrence, was screened in the UK on ITV in 1970. The second series, directed by David Reid and David Lane, was shown in 1971. Each series had thirteen episodes, most but not all written by Carpenter, who also published two books based on the scripts.;df9fb9fa-f121-45b5-8be5-8ff7ad5219a2;22;150;JPY;13
diff --git a/samples/bookshop/db/data/sap.capire.bookshop-Books_texts.csv b/samples/bookshop/db/data/sap.capire.bookshop-Books_texts.csv
new file mode 100644
index 000000000..3a3465b28
--- /dev/null
+++ b/samples/bookshop/db/data/sap.capire.bookshop-Books_texts.csv
@@ -0,0 +1,5 @@
+ID_texts;ID;locale;title;descr
+52eee553-266d-4fdd-a5ca-909910e76ae4;aeeda49f-72f2-4880-be27-a513b2e53040;de;Sturmhöhe;Sturmhöhe (Originaltitel: Wuthering Heights) ist der einzige Roman der englischen Schriftstellerin Emily Brontë (1818–1848). Der 1847 unter dem Pseudonym Ellis Bell veröffentlichte Roman wurde vom viktorianischen Publikum weitgehend abgelehnt, heute gilt er als ein Klassiker der britischen Romanliteratur des 19. Jahrhunderts.
+54e58142-f06e-49c1-a51d-138f86cea34e;aeeda49f-72f2-4880-be27-a513b2e53040;fr;Les Hauts de Hurlevent;Les Hauts de Hurlevent (titre original : Wuthering Heights), parfois orthographié Les Hauts de Hurle-Vent, est l'unique roman d'Emily Brontë, publié pour la première fois en 1847 sous le pseudonyme d’Ellis Bell. Loin d'être un récit moralisateur, Emily Brontë achève néanmoins le roman dans une atmosphère sereine, suggérant le triomphe de la paix et du Bien sur la vengeance et le Mal.
+bbbf8a88-797d-4790-af1c-1cc857718ee0;b0056977-4cf5-46a2-ab14-6409ee2e0df1;de;Jane Eyre;Jane Eyre. Eine Autobiographie (Originaltitel: Jane Eyre. An Autobiography), erstmals erschienen im Jahr 1847 unter dem Pseudonym Currer Bell, ist der erste veröffentlichte Roman der britischen Autorin Charlotte Brontë und ein Klassiker der viktorianischen Romanliteratur des 19. Jahrhunderts. Der Roman erzählt in Form einer Ich-Erzählung die Lebensgeschichte von Jane Eyre (ausgesprochen /ˌdʒeɪn ˈɛə/), die nach einer schweren Kindheit eine Stelle als Gouvernante annimmt und sich in ihren Arbeitgeber verliebt, jedoch immer wieder um ihre Freiheit und Selbstbestimmung kämpfen muss. Als klein, dünn, blass, stets schlicht dunkel gekleidet und mit strengem Mittelscheitel beschrieben, gilt die Heldin des Romans Jane Eyre nicht zuletzt aufgrund der Kino- und Fernsehversionen der melodramatischen Romanvorlage als die bekannteste englische Gouvernante der Literaturgeschichte
+a90d4378-1a3e-48e7-b60b-5670e78807e1;7756b725-cefc-43a2-a3c8-0c9104a349b8;de;Eleonora;“Eleonora” ist eine Erzählung von Edgar Allan Poe. Sie wurde 1841 erstveröffentlicht. In ihr geht es um das Paradox der Treue in der Treulosigkeit.
diff --git a/samples/bookshop/db/data/sap.capire.bookshop-Genres.csv b/samples/bookshop/db/data/sap.capire.bookshop-Genres.csv
new file mode 100644
index 000000000..1ea3793bb
--- /dev/null
+++ b/samples/bookshop/db/data/sap.capire.bookshop-Genres.csv
@@ -0,0 +1,16 @@
+ID;parent_ID;name
+10;;Fiction
+11;10;Drama
+12;10;Poetry
+13;10;Fantasy
+14;10;Science Fiction
+15;10;Romance
+16;10;Mystery
+17;10;Thriller
+18;10;Dystopia
+19;10;Fairy Tale
+20;;Non-Fiction
+21;20;Biography
+22;21;Autobiography
+23;20;Essay
+24;20;Speech
diff --git a/samples/bookshop/db/schema.cds b/samples/bookshop/db/schema.cds
new file mode 100644
index 000000000..1aedfbaca
--- /dev/null
+++ b/samples/bookshop/db/schema.cds
@@ -0,0 +1,37 @@
+using {
+ Currency,
+ managed,
+ cuid,
+ sap.common.CodeList
+} from '@sap/cds/common';
+
+namespace sap.capire.bookshop;
+
+entity Books : managed, cuid {
+ @mandatory title : localized String(111);
+ descr : localized String(1111);
+ @mandatory author : Association to Authors;
+ genre : Association to Genres;
+ stock : Integer;
+ price : Decimal;
+ currency : Currency;
+ image : LargeBinary @Core.MediaType: 'image/png';
+}
+
+entity Authors : managed, cuid {
+ @mandatory name : String(111);
+ dateOfBirth : Date;
+ dateOfDeath : Date;
+ placeOfBirth : String;
+ placeOfDeath : String;
+ books : Association to many Books
+ on books.author = $self;
+}
+
+/** Hierarchically organized Code List for Genres */
+entity Genres : CodeList {
+ key ID : Integer;
+ parent : Association to Genres;
+ children : Composition of many Genres
+ on children.parent = $self;
+}
diff --git a/samples/bookshop/package-lock.json b/samples/bookshop/package-lock.json
new file mode 100644
index 000000000..2527e0311
--- /dev/null
+++ b/samples/bookshop/package-lock.json
@@ -0,0 +1,2022 @@
+{
+ "name": "bookshop-cds",
+ "version": "1.0.0",
+ "lockfileVersion": 3,
+ "requires": true,
+ "packages": {
+ "": {
+ "name": "bookshop-cds",
+ "version": "1.0.0",
+ "license": "ISC",
+ "devDependencies": {
+ "@sap/cds-dk": "^9.3.2"
+ }
+ },
+ "node_modules/@sap/cds-dk": {
+ "version": "9.4.3",
+ "resolved": "https://registry.npmjs.org/@sap/cds-dk/-/cds-dk-9.4.3.tgz",
+ "integrity": "sha512-kVz08dhBF7Zms1disoXUoEIrR/ctJkZd7gky1I/sww4fwl832elW6ZxTs85HLn+LeF0Gr3/HX+jJoqRy+3GYNg==",
+ "dev": true,
+ "hasShrinkwrap": true,
+ "license": "SEE LICENSE IN LICENSE",
+ "dependencies": {
+ "@cap-js/asyncapi": "^1.0.0",
+ "@cap-js/openapi": "^1.0.0",
+ "@sap/cds": ">=8.3",
+ "@sap/cds-mtxs": ">=2",
+ "@sap/hdi-deploy": "^5",
+ "axios": "^1",
+ "express": "^4.17.3",
+ "hdb": "^0",
+ "livereload-js": "^4.0.1",
+ "mustache": "^4.0.1",
+ "node-watch": ">=0.7",
+ "ws": "^8.4.2",
+ "xml-js": "^1.6.11",
+ "yaml": "^2"
+ },
+ "bin": {
+ "cds": "bin/cds.js",
+ "cds-ts": "bin/cds-ts.js",
+ "cds-tsx": "bin/cds-tsx.js"
+ },
+ "optionalDependencies": {
+ "@cap-js/sqlite": ">=1"
+ }
+ },
+ "node_modules/@sap/cds-dk/node_modules/@cap-js/asyncapi": {
+ "version": "1.0.3",
+ "integrity": "sha512-vZSWKAe+3qfvZDXV5SSFiObGWmqyS9MDyEADb5PLVT8kzO39qGaSDPv/GzI/gwvRfCayGAjU4ThiBKrFA7Gclg==",
+ "dev": true,
+ "license": "SEE LICENSE IN LICENSE",
+ "peerDependencies": {
+ "@sap/cds": ">=7.6"
+ }
+ },
+ "node_modules/@sap/cds-dk/node_modules/@cap-js/db-service": {
+ "version": "2.6.0",
+ "integrity": "sha512-t72/FcAYFbPdx+5iV+lVKcwF2MLOx8II3jJdlC1dX/KXQORoS3wDFwWbakP0f/eharE5hfa7KMFJqrSMtDigbQ==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "optional": true,
+ "dependencies": {
+ "generic-pool": "^3.9.0"
+ },
+ "peerDependencies": {
+ "@sap/cds": ">=9"
+ }
+ },
+ "node_modules/@sap/cds-dk/node_modules/@cap-js/openapi": {
+ "version": "1.2.3",
+ "integrity": "sha512-UnEUBrBIjMvYYJTtAmSrnWLKIjnaK9KcCS6pPoVBRgZrMaL0bl/aB3KMH4xzc6LWjtbxzlyI71XC7No4+SKerg==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "dependencies": {
+ "pluralize": "^8.0.0"
+ },
+ "peerDependencies": {
+ "@sap/cds": ">=7.6"
+ }
+ },
+ "node_modules/@sap/cds-dk/node_modules/@cap-js/sqlite": {
+ "version": "2.0.4",
+ "integrity": "sha512-QPVkycLJG6EubtjrPeiK4dTI1zPH/nabvhiYnTeg2AbeQ8mbazm5pjmcLrzOOKF/5bGS8KQo2J+49fU5LPRR3A==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "optional": true,
+ "dependencies": {
+ "@cap-js/db-service": "^2.6.0",
+ "better-sqlite3": "^12.0.0"
+ },
+ "peerDependencies": {
+ "@sap/cds": ">=9"
+ }
+ },
+ "node_modules/@sap/cds-dk/node_modules/@eslint/js": {
+ "version": "9.38.0",
+ "integrity": "sha512-UZ1VpFvXf9J06YG9xQBdnzU+kthors6KjhMAl6f4gH4usHyh31rUf2DLGInT8RFYIReYXNSydgPY0V2LuWgl7A==",
+ "dev": true,
+ "license": "MIT",
+ "peer": true,
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ },
+ "funding": {
+ "url": "https://eslint.org/donate"
+ }
+ },
+ "node_modules/@sap/cds-dk/node_modules/@sap/cds": {
+ "version": "9.4.4",
+ "integrity": "sha512-JJCHeEJF4xzFyZSf2ToocvVE9dyHfNLTRXOauOxlmpfyaLg97G7Qp+L4bD132eB0onBG9bQj3eH8DzBm0hVvIw==",
+ "dev": true,
+ "license": "SEE LICENSE IN LICENSE",
+ "dependencies": {
+ "@sap/cds-compiler": "^6.3",
+ "@sap/cds-fiori": "^2",
+ "js-yaml": "^4.1.0"
+ },
+ "bin": {
+ "cds-deploy": "bin/deploy.js",
+ "cds-serve": "bin/serve.js"
+ },
+ "engines": {
+ "node": ">=20"
+ },
+ "peerDependencies": {
+ "@eslint/js": "^9",
+ "express": "^4",
+ "tar": "^7"
+ },
+ "peerDependenciesMeta": {
+ "express": {
+ "optional": true
+ },
+ "tar": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@sap/cds-dk/node_modules/@sap/cds-compiler": {
+ "version": "6.4.6",
+ "integrity": "sha512-auAjRh9t0KKj4LiGAr/fxikZRIngx9YXVHTJWf0LeaGv0ZpYOi6iWbSnU1XRB2e6hsf+Ou1w5oTOHooC5sZfog==",
+ "dev": true,
+ "license": "SEE LICENSE IN LICENSE",
+ "bin": {
+ "cdsc": "bin/cdsc.js",
+ "cdshi": "bin/cdshi.js",
+ "cdsse": "bin/cdsse.js"
+ },
+ "engines": {
+ "node": ">=20"
+ }
+ },
+ "node_modules/@sap/cds-dk/node_modules/@sap/cds-fiori": {
+ "version": "2.1.1",
+ "integrity": "sha512-X+4v4LBAT8HIt0zr28/kJNS15nlNlcM97vAMW+agLrmK134nyBiMwUMcp8BMhxlG9B2PykrnAKH56D9O3tfoBg==",
+ "dev": true,
+ "license": "SEE LICENSE IN LICENSE",
+ "peerDependencies": {
+ "@sap/cds": ">=8",
+ "express": "^4"
+ }
+ },
+ "node_modules/@sap/cds-dk/node_modules/@sap/cds-mtxs": {
+ "version": "3.4.3",
+ "integrity": "sha512-vgABFr7huaKWGx2fWHeGom5bVgsQKD7/gqkC7aQ/7yC9hdZdrx0mz4iZ0ASHUZ5PZWp2FWLD+eaJ9sXKUGHgpA==",
+ "dev": true,
+ "license": "SEE LICENSE IN LICENSE",
+ "dependencies": {
+ "@sap/hdi-deploy": "^5"
+ },
+ "bin": {
+ "cds-mtx": "bin/cds-mtx.js",
+ "cds-mtx-migrate": "bin/cds-mtx-migrate.js"
+ },
+ "peerDependencies": {
+ "@sap/cds": "^9"
+ }
+ },
+ "node_modules/@sap/cds-dk/node_modules/@sap/hdi": {
+ "version": "4.8.0",
+ "integrity": "sha512-tkJmY2ffm6mt4/LFwRBihlQkMxNAXa3ngvRe2N/6+qLIsUNdrH/M03S5mkygXq56K+KoVVZYuradajCusMWwsw==",
+ "dev": true,
+ "license": "See LICENSE file",
+ "dependencies": {
+ "async": "^3.2.3"
+ },
+ "engines": {
+ "node": ">=18"
+ },
+ "peerDependencies": {
+ "@sap/hana-client": "^2 >= 2.5",
+ "hdb": "^2 || ^0"
+ },
+ "peerDependenciesMeta": {
+ "@sap/hana-client": {
+ "optional": true
+ },
+ "hdb": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@sap/cds-dk/node_modules/@sap/hdi-deploy": {
+ "version": "5.5.1",
+ "integrity": "sha512-5r9SIkXX7cO+MwRFF32O566sMx6LP1mLin0eT9F+Adqy+0SrdwkWv4JslQzYetiWLuNsfqQljcao62alaxts8A==",
+ "dev": true,
+ "license": "See LICENSE file",
+ "dependencies": {
+ "@sap/hdi": "^4.8.0",
+ "@sap/xsenv": "^5.2.0",
+ "async": "^3.2.6",
+ "dotenv": "^16.4.5",
+ "handlebars": "^4.7.8",
+ "micromatch": "^4.0.8"
+ },
+ "engines": {
+ "node": ">=18.x"
+ },
+ "peerDependencies": {
+ "@sap/hana-client": "^2 >= 2.6",
+ "hdb": "^2 || ^0"
+ },
+ "peerDependenciesMeta": {
+ "@sap/hana-client": {
+ "optional": true
+ },
+ "hdb": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@sap/cds-dk/node_modules/@sap/xsenv": {
+ "version": "5.6.1",
+ "integrity": "sha512-4pDpsYLNJsLUBWtTSG+TJ8ul5iY0dWDyJgTy2H/WZGZww9CSPLP/39x+syDDTjkggsmZAlo9t7y9TiXMmtAunw==",
+ "dev": true,
+ "license": "SEE LICENSE IN LICENSE file",
+ "dependencies": {
+ "debug": "4.4.0",
+ "node-cache": "^5.1.2",
+ "verror": "1.10.1"
+ },
+ "engines": {
+ "node": "^18.0.0 || ^20.0.0 || ^22.0.0"
+ }
+ },
+ "node_modules/@sap/cds-dk/node_modules/accepts": {
+ "version": "1.3.8",
+ "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "mime-types": "~2.1.34",
+ "negotiator": "0.6.3"
+ },
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/@sap/cds-dk/node_modules/argparse": {
+ "version": "2.0.1",
+ "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==",
+ "dev": true,
+ "license": "Python-2.0"
+ },
+ "node_modules/@sap/cds-dk/node_modules/array-flatten": {
+ "version": "1.1.1",
+ "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@sap/cds-dk/node_modules/assert-plus": {
+ "version": "1.0.0",
+ "integrity": "sha512-NfJ4UzBCcQGLDlQq7nHxH+tv3kyZ0hHQqF5BO6J7tNJeP5do1llPr8dZ8zHonfhAu0PHAdMkSo+8o0wxg9lZWw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.8"
+ }
+ },
+ "node_modules/@sap/cds-dk/node_modules/async": {
+ "version": "3.2.6",
+ "integrity": "sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@sap/cds-dk/node_modules/asynckit": {
+ "version": "0.4.0",
+ "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@sap/cds-dk/node_modules/axios": {
+ "version": "1.13.1",
+ "integrity": "sha512-hU4EGxxt+j7TQijx1oYdAjw4xuIp1wRQSsbMFwSthCWeBQur1eF+qJ5iQ5sN3Tw8YRzQNKb8jszgBdMDVqwJcw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "follow-redirects": "^1.15.6",
+ "form-data": "^4.0.4",
+ "proxy-from-env": "^1.1.0"
+ }
+ },
+ "node_modules/@sap/cds-dk/node_modules/base64-js": {
+ "version": "1.5.1",
+ "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/feross"
+ },
+ {
+ "type": "patreon",
+ "url": "https://www.patreon.com/feross"
+ },
+ {
+ "type": "consulting",
+ "url": "https://feross.org/support"
+ }
+ ],
+ "license": "MIT",
+ "optional": true
+ },
+ "node_modules/@sap/cds-dk/node_modules/better-sqlite3": {
+ "version": "12.4.1",
+ "integrity": "sha512-3yVdyZhklTiNrtg+4WqHpJpFDd+WHTg2oM7UcR80GqL05AOV0xEJzc6qNvFYoEtE+hRp1n9MpN6/+4yhlGkDXQ==",
+ "dev": true,
+ "hasInstallScript": true,
+ "license": "MIT",
+ "optional": true,
+ "dependencies": {
+ "bindings": "^1.5.0",
+ "prebuild-install": "^7.1.1"
+ },
+ "engines": {
+ "node": "20.x || 22.x || 23.x || 24.x"
+ }
+ },
+ "node_modules/@sap/cds-dk/node_modules/bindings": {
+ "version": "1.5.0",
+ "integrity": "sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ==",
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "dependencies": {
+ "file-uri-to-path": "1.0.0"
+ }
+ },
+ "node_modules/@sap/cds-dk/node_modules/bl": {
+ "version": "4.1.0",
+ "integrity": "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==",
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "dependencies": {
+ "buffer": "^5.5.0",
+ "inherits": "^2.0.4",
+ "readable-stream": "^3.4.0"
+ }
+ },
+ "node_modules/@sap/cds-dk/node_modules/body-parser": {
+ "version": "1.20.3",
+ "integrity": "sha512-7rAxByjUMqQ3/bHJy7D6OGXvx/MMc4IqBn/X0fcM1QUcAItpZrBEYhWGem+tzXH90c+G01ypMcYJBO9Y30203g==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "bytes": "3.1.2",
+ "content-type": "~1.0.5",
+ "debug": "2.6.9",
+ "depd": "2.0.0",
+ "destroy": "1.2.0",
+ "http-errors": "2.0.0",
+ "iconv-lite": "0.4.24",
+ "on-finished": "2.4.1",
+ "qs": "6.13.0",
+ "raw-body": "2.5.2",
+ "type-is": "~1.6.18",
+ "unpipe": "1.0.0"
+ },
+ "engines": {
+ "node": ">= 0.8",
+ "npm": "1.2.8000 || >= 1.4.16"
+ }
+ },
+ "node_modules/@sap/cds-dk/node_modules/body-parser/node_modules/debug": {
+ "version": "2.6.9",
+ "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "ms": "2.0.0"
+ }
+ },
+ "node_modules/@sap/cds-dk/node_modules/body-parser/node_modules/ms": {
+ "version": "2.0.0",
+ "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@sap/cds-dk/node_modules/braces": {
+ "version": "3.0.3",
+ "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "fill-range": "^7.1.1"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/@sap/cds-dk/node_modules/buffer": {
+ "version": "5.7.1",
+ "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/feross"
+ },
+ {
+ "type": "patreon",
+ "url": "https://www.patreon.com/feross"
+ },
+ {
+ "type": "consulting",
+ "url": "https://feross.org/support"
+ }
+ ],
+ "license": "MIT",
+ "optional": true,
+ "dependencies": {
+ "base64-js": "^1.3.1",
+ "ieee754": "^1.1.13"
+ }
+ },
+ "node_modules/@sap/cds-dk/node_modules/bytes": {
+ "version": "3.1.2",
+ "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/@sap/cds-dk/node_modules/call-bind-apply-helpers": {
+ "version": "1.0.2",
+ "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "es-errors": "^1.3.0",
+ "function-bind": "^1.1.2"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/@sap/cds-dk/node_modules/call-bound": {
+ "version": "1.0.4",
+ "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "call-bind-apply-helpers": "^1.0.2",
+ "get-intrinsic": "^1.3.0"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/@sap/cds-dk/node_modules/chownr": {
+ "version": "1.1.4",
+ "integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==",
+ "dev": true,
+ "license": "ISC",
+ "optional": true
+ },
+ "node_modules/@sap/cds-dk/node_modules/clone": {
+ "version": "2.1.2",
+ "integrity": "sha512-3Pe/CF1Nn94hyhIYpjtiLhdCoEoz0DqQ+988E9gmeEdQZlojxnOb74wctFyuwWQHzqyf9X7C7MG8juUpqBJT8w==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.8"
+ }
+ },
+ "node_modules/@sap/cds-dk/node_modules/combined-stream": {
+ "version": "1.0.8",
+ "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "delayed-stream": "~1.0.0"
+ },
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/@sap/cds-dk/node_modules/content-disposition": {
+ "version": "0.5.4",
+ "integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "safe-buffer": "5.2.1"
+ },
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/@sap/cds-dk/node_modules/content-type": {
+ "version": "1.0.5",
+ "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/@sap/cds-dk/node_modules/cookie": {
+ "version": "0.7.1",
+ "integrity": "sha512-6DnInpx7SJ2AK3+CTUE/ZM0vWTUboZCegxhC2xiIydHR9jNuTAASBrfEpHhiGOZw/nX51bHt6YQl8jsGo4y/0w==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/@sap/cds-dk/node_modules/cookie-signature": {
+ "version": "1.0.6",
+ "integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@sap/cds-dk/node_modules/core-util-is": {
+ "version": "1.0.2",
+ "integrity": "sha512-3lqz5YjWTYnW6dlDa5TLaTCcShfar1e40rmcJVwCBJC6mWlFuj0eCHIElmG1g5kyuJ/GD+8Wn4FFCcz4gJPfaQ==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@sap/cds-dk/node_modules/debug": {
+ "version": "4.4.0",
+ "integrity": "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "ms": "^2.1.3"
+ },
+ "engines": {
+ "node": ">=6.0"
+ },
+ "peerDependenciesMeta": {
+ "supports-color": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@sap/cds-dk/node_modules/decompress-response": {
+ "version": "6.0.0",
+ "integrity": "sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==",
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "dependencies": {
+ "mimic-response": "^3.1.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/@sap/cds-dk/node_modules/deep-extend": {
+ "version": "0.6.0",
+ "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==",
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "engines": {
+ "node": ">=4.0.0"
+ }
+ },
+ "node_modules/@sap/cds-dk/node_modules/delayed-stream": {
+ "version": "1.0.0",
+ "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.4.0"
+ }
+ },
+ "node_modules/@sap/cds-dk/node_modules/depd": {
+ "version": "2.0.0",
+ "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/@sap/cds-dk/node_modules/destroy": {
+ "version": "1.2.0",
+ "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.8",
+ "npm": "1.2.8000 || >= 1.4.16"
+ }
+ },
+ "node_modules/@sap/cds-dk/node_modules/detect-libc": {
+ "version": "2.1.2",
+ "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "optional": true,
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/@sap/cds-dk/node_modules/dotenv": {
+ "version": "16.6.1",
+ "integrity": "sha512-uBq4egWHTcTt33a72vpSG0z3HnPuIl6NqYcTrKEg2azoEyl2hpW0zqlxysq2pK9HlDIHyHyakeYaYnSAwd8bow==",
+ "dev": true,
+ "license": "BSD-2-Clause",
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://dotenvx.com"
+ }
+ },
+ "node_modules/@sap/cds-dk/node_modules/dunder-proto": {
+ "version": "1.0.1",
+ "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "call-bind-apply-helpers": "^1.0.1",
+ "es-errors": "^1.3.0",
+ "gopd": "^1.2.0"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/@sap/cds-dk/node_modules/ee-first": {
+ "version": "1.1.1",
+ "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@sap/cds-dk/node_modules/encodeurl": {
+ "version": "2.0.0",
+ "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/@sap/cds-dk/node_modules/end-of-stream": {
+ "version": "1.4.5",
+ "integrity": "sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg==",
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "dependencies": {
+ "once": "^1.4.0"
+ }
+ },
+ "node_modules/@sap/cds-dk/node_modules/es-define-property": {
+ "version": "1.0.1",
+ "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/@sap/cds-dk/node_modules/es-errors": {
+ "version": "1.3.0",
+ "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/@sap/cds-dk/node_modules/es-object-atoms": {
+ "version": "1.1.1",
+ "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "es-errors": "^1.3.0"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/@sap/cds-dk/node_modules/es-set-tostringtag": {
+ "version": "2.1.0",
+ "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "es-errors": "^1.3.0",
+ "get-intrinsic": "^1.2.6",
+ "has-tostringtag": "^1.0.2",
+ "hasown": "^2.0.2"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/@sap/cds-dk/node_modules/escape-html": {
+ "version": "1.0.3",
+ "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@sap/cds-dk/node_modules/etag": {
+ "version": "1.8.1",
+ "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/@sap/cds-dk/node_modules/expand-template": {
+ "version": "2.0.3",
+ "integrity": "sha512-XYfuKMvj4O35f/pOXLObndIRvyQ+/+6AhODh+OKWj9S9498pHHn/IMszH+gt0fBCRWMNfk1ZSp5x3AifmnI2vg==",
+ "dev": true,
+ "license": "(MIT OR WTFPL)",
+ "optional": true,
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/@sap/cds-dk/node_modules/express": {
+ "version": "4.21.2",
+ "integrity": "sha512-28HqgMZAmih1Czt9ny7qr6ek2qddF4FclbMzwhCREB6OFfH+rXAnuNCwo1/wFvrtbgsQDb4kSbX9de9lFbrXnA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "accepts": "~1.3.8",
+ "array-flatten": "1.1.1",
+ "body-parser": "1.20.3",
+ "content-disposition": "0.5.4",
+ "content-type": "~1.0.4",
+ "cookie": "0.7.1",
+ "cookie-signature": "1.0.6",
+ "debug": "2.6.9",
+ "depd": "2.0.0",
+ "encodeurl": "~2.0.0",
+ "escape-html": "~1.0.3",
+ "etag": "~1.8.1",
+ "finalhandler": "1.3.1",
+ "fresh": "0.5.2",
+ "http-errors": "2.0.0",
+ "merge-descriptors": "1.0.3",
+ "methods": "~1.1.2",
+ "on-finished": "2.4.1",
+ "parseurl": "~1.3.3",
+ "path-to-regexp": "0.1.12",
+ "proxy-addr": "~2.0.7",
+ "qs": "6.13.0",
+ "range-parser": "~1.2.1",
+ "safe-buffer": "5.2.1",
+ "send": "0.19.0",
+ "serve-static": "1.16.2",
+ "setprototypeof": "1.2.0",
+ "statuses": "2.0.1",
+ "type-is": "~1.6.18",
+ "utils-merge": "1.0.1",
+ "vary": "~1.1.2"
+ },
+ "engines": {
+ "node": ">= 0.10.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/express"
+ }
+ },
+ "node_modules/@sap/cds-dk/node_modules/express/node_modules/debug": {
+ "version": "2.6.9",
+ "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "ms": "2.0.0"
+ }
+ },
+ "node_modules/@sap/cds-dk/node_modules/express/node_modules/ms": {
+ "version": "2.0.0",
+ "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@sap/cds-dk/node_modules/extsprintf": {
+ "version": "1.4.1",
+ "integrity": "sha512-Wrk35e8ydCKDj/ArClo1VrPVmN8zph5V4AtHwIuHhvMXsKf73UT3BOD+azBIW+3wOJ4FhEH7zyaJCFvChjYvMA==",
+ "dev": true,
+ "engines": [
+ "node >=0.6.0"
+ ],
+ "license": "MIT"
+ },
+ "node_modules/@sap/cds-dk/node_modules/file-uri-to-path": {
+ "version": "1.0.0",
+ "integrity": "sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw==",
+ "dev": true,
+ "license": "MIT",
+ "optional": true
+ },
+ "node_modules/@sap/cds-dk/node_modules/fill-range": {
+ "version": "7.1.1",
+ "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "to-regex-range": "^5.0.1"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/@sap/cds-dk/node_modules/finalhandler": {
+ "version": "1.3.1",
+ "integrity": "sha512-6BN9trH7bp3qvnrRyzsBz+g3lZxTNZTbVO2EV1CS0WIcDbawYVdYvGflME/9QP0h0pYlCDBCTjYa9nZzMDpyxQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "debug": "2.6.9",
+ "encodeurl": "~2.0.0",
+ "escape-html": "~1.0.3",
+ "on-finished": "2.4.1",
+ "parseurl": "~1.3.3",
+ "statuses": "2.0.1",
+ "unpipe": "~1.0.0"
+ },
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/@sap/cds-dk/node_modules/finalhandler/node_modules/debug": {
+ "version": "2.6.9",
+ "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "ms": "2.0.0"
+ }
+ },
+ "node_modules/@sap/cds-dk/node_modules/finalhandler/node_modules/ms": {
+ "version": "2.0.0",
+ "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@sap/cds-dk/node_modules/follow-redirects": {
+ "version": "1.15.11",
+ "integrity": "sha512-deG2P0JfjrTxl50XGCDyfI97ZGVCxIpfKYmfyrQ54n5FO/0gfIES8C/Psl6kWVDolizcaaxZJnTS0QSMxvnsBQ==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "individual",
+ "url": "https://github.com/sponsors/RubenVerborgh"
+ }
+ ],
+ "license": "MIT",
+ "engines": {
+ "node": ">=4.0"
+ },
+ "peerDependenciesMeta": {
+ "debug": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@sap/cds-dk/node_modules/form-data": {
+ "version": "4.0.4",
+ "integrity": "sha512-KrGhL9Q4zjj0kiUt5OO4Mr/A/jlI2jDYs5eHBpYHPcBEVSiipAvn2Ko2HnPe20rmcuuvMHNdZFp+4IlGTMF0Ow==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "asynckit": "^0.4.0",
+ "combined-stream": "^1.0.8",
+ "es-set-tostringtag": "^2.1.0",
+ "hasown": "^2.0.2",
+ "mime-types": "^2.1.12"
+ },
+ "engines": {
+ "node": ">= 6"
+ }
+ },
+ "node_modules/@sap/cds-dk/node_modules/forwarded": {
+ "version": "0.2.0",
+ "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/@sap/cds-dk/node_modules/fresh": {
+ "version": "0.5.2",
+ "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/@sap/cds-dk/node_modules/fs-constants": {
+ "version": "1.0.0",
+ "integrity": "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==",
+ "dev": true,
+ "license": "MIT",
+ "optional": true
+ },
+ "node_modules/@sap/cds-dk/node_modules/function-bind": {
+ "version": "1.1.2",
+ "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==",
+ "dev": true,
+ "license": "MIT",
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/@sap/cds-dk/node_modules/generic-pool": {
+ "version": "3.9.0",
+ "integrity": "sha512-hymDOu5B53XvN4QT9dBmZxPX4CWhBPPLguTZ9MMFeFa/Kg0xWVfylOVNlJji/E7yTZWFd/q9GO5TxDLq156D7g==",
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "engines": {
+ "node": ">= 4"
+ }
+ },
+ "node_modules/@sap/cds-dk/node_modules/get-intrinsic": {
+ "version": "1.3.0",
+ "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "call-bind-apply-helpers": "^1.0.2",
+ "es-define-property": "^1.0.1",
+ "es-errors": "^1.3.0",
+ "es-object-atoms": "^1.1.1",
+ "function-bind": "^1.1.2",
+ "get-proto": "^1.0.1",
+ "gopd": "^1.2.0",
+ "has-symbols": "^1.1.0",
+ "hasown": "^2.0.2",
+ "math-intrinsics": "^1.1.0"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/@sap/cds-dk/node_modules/get-proto": {
+ "version": "1.0.1",
+ "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "dunder-proto": "^1.0.1",
+ "es-object-atoms": "^1.0.0"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/@sap/cds-dk/node_modules/github-from-package": {
+ "version": "0.0.0",
+ "integrity": "sha512-SyHy3T1v2NUXn29OsWdxmK6RwHD+vkj3v8en8AOBZ1wBQ/hCAQ5bAQTD02kW4W9tUp/3Qh6J8r9EvntiyCmOOw==",
+ "dev": true,
+ "license": "MIT",
+ "optional": true
+ },
+ "node_modules/@sap/cds-dk/node_modules/gopd": {
+ "version": "1.2.0",
+ "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/@sap/cds-dk/node_modules/handlebars": {
+ "version": "4.7.8",
+ "integrity": "sha512-vafaFqs8MZkRrSX7sFVUdo3ap/eNiLnb4IakshzvP56X5Nr1iGKAIqdX6tMlm6HcNRIkr6AxO5jFEoJzzpT8aQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "minimist": "^1.2.5",
+ "neo-async": "^2.6.2",
+ "source-map": "^0.6.1",
+ "wordwrap": "^1.0.0"
+ },
+ "bin": {
+ "handlebars": "bin/handlebars"
+ },
+ "engines": {
+ "node": ">=0.4.7"
+ },
+ "optionalDependencies": {
+ "uglify-js": "^3.1.4"
+ }
+ },
+ "node_modules/@sap/cds-dk/node_modules/has-symbols": {
+ "version": "1.1.0",
+ "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/@sap/cds-dk/node_modules/has-tostringtag": {
+ "version": "1.0.2",
+ "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "has-symbols": "^1.0.3"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/@sap/cds-dk/node_modules/hasown": {
+ "version": "2.0.2",
+ "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "function-bind": "^1.1.2"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/@sap/cds-dk/node_modules/hdb": {
+ "version": "0.19.12",
+ "integrity": "sha512-vv+cjmvr6fNH/s0Q2zOZc4sEjMpSC0KuacFn8dp3L38qM3RA2LLeX70wWhZLESpwvwUf1pQkRfUhZeooFSmv3A==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "dependencies": {
+ "iconv-lite": "^0.4.18"
+ },
+ "engines": {
+ "node": ">= 0.12"
+ }
+ },
+ "node_modules/@sap/cds-dk/node_modules/http-errors": {
+ "version": "2.0.0",
+ "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "depd": "2.0.0",
+ "inherits": "2.0.4",
+ "setprototypeof": "1.2.0",
+ "statuses": "2.0.1",
+ "toidentifier": "1.0.1"
+ },
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/@sap/cds-dk/node_modules/iconv-lite": {
+ "version": "0.4.24",
+ "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "safer-buffer": ">= 2.1.2 < 3"
+ },
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/@sap/cds-dk/node_modules/ieee754": {
+ "version": "1.2.1",
+ "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/feross"
+ },
+ {
+ "type": "patreon",
+ "url": "https://www.patreon.com/feross"
+ },
+ {
+ "type": "consulting",
+ "url": "https://feross.org/support"
+ }
+ ],
+ "license": "BSD-3-Clause",
+ "optional": true
+ },
+ "node_modules/@sap/cds-dk/node_modules/inherits": {
+ "version": "2.0.4",
+ "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==",
+ "dev": true,
+ "license": "ISC"
+ },
+ "node_modules/@sap/cds-dk/node_modules/ini": {
+ "version": "1.3.8",
+ "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==",
+ "dev": true,
+ "license": "ISC",
+ "optional": true
+ },
+ "node_modules/@sap/cds-dk/node_modules/ipaddr.js": {
+ "version": "1.9.1",
+ "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.10"
+ }
+ },
+ "node_modules/@sap/cds-dk/node_modules/is-number": {
+ "version": "7.0.0",
+ "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.12.0"
+ }
+ },
+ "node_modules/@sap/cds-dk/node_modules/js-yaml": {
+ "version": "4.1.0",
+ "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "argparse": "^2.0.1"
+ },
+ "bin": {
+ "js-yaml": "bin/js-yaml.js"
+ }
+ },
+ "node_modules/@sap/cds-dk/node_modules/livereload-js": {
+ "version": "4.0.2",
+ "integrity": "sha512-Fy7VwgQNiOkynYyNBTo3v9hQUhcW5pFAheJN148+DTgpShjsy/22pLHKKwDK5v0kOsZsJBK+6q1PMgLvRmrwFQ==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@sap/cds-dk/node_modules/math-intrinsics": {
+ "version": "1.1.0",
+ "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/@sap/cds-dk/node_modules/media-typer": {
+ "version": "0.3.0",
+ "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/@sap/cds-dk/node_modules/merge-descriptors": {
+ "version": "1.0.3",
+ "integrity": "sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ==",
+ "dev": true,
+ "license": "MIT",
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/@sap/cds-dk/node_modules/methods": {
+ "version": "1.1.2",
+ "integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/@sap/cds-dk/node_modules/micromatch": {
+ "version": "4.0.8",
+ "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "braces": "^3.0.3",
+ "picomatch": "^2.3.1"
+ },
+ "engines": {
+ "node": ">=8.6"
+ }
+ },
+ "node_modules/@sap/cds-dk/node_modules/micromatch/node_modules/picomatch": {
+ "version": "2.3.1",
+ "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8.6"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/jonschlinkert"
+ }
+ },
+ "node_modules/@sap/cds-dk/node_modules/mime": {
+ "version": "1.6.0",
+ "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==",
+ "dev": true,
+ "license": "MIT",
+ "bin": {
+ "mime": "cli.js"
+ },
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/@sap/cds-dk/node_modules/mime-db": {
+ "version": "1.52.0",
+ "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/@sap/cds-dk/node_modules/mime-types": {
+ "version": "2.1.35",
+ "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "mime-db": "1.52.0"
+ },
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/@sap/cds-dk/node_modules/mimic-response": {
+ "version": "3.1.0",
+ "integrity": "sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==",
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/@sap/cds-dk/node_modules/minimist": {
+ "version": "1.2.8",
+ "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==",
+ "dev": true,
+ "license": "MIT",
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/@sap/cds-dk/node_modules/mkdirp-classic": {
+ "version": "0.5.3",
+ "integrity": "sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==",
+ "dev": true,
+ "license": "MIT",
+ "optional": true
+ },
+ "node_modules/@sap/cds-dk/node_modules/ms": {
+ "version": "2.1.3",
+ "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@sap/cds-dk/node_modules/mustache": {
+ "version": "4.2.0",
+ "integrity": "sha512-71ippSywq5Yb7/tVYyGbkBggbU8H3u5Rz56fH60jGFgr8uHwxs+aSKeqmluIVzM0m0kB7xQjKS6qPfd0b2ZoqQ==",
+ "dev": true,
+ "license": "MIT",
+ "bin": {
+ "mustache": "bin/mustache"
+ }
+ },
+ "node_modules/@sap/cds-dk/node_modules/napi-build-utils": {
+ "version": "2.0.0",
+ "integrity": "sha512-GEbrYkbfF7MoNaoh2iGG84Mnf/WZfB0GdGEsM8wz7Expx/LlWf5U8t9nvJKXSp3qr5IsEbK04cBGhol/KwOsWA==",
+ "dev": true,
+ "license": "MIT",
+ "optional": true
+ },
+ "node_modules/@sap/cds-dk/node_modules/negotiator": {
+ "version": "0.6.3",
+ "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/@sap/cds-dk/node_modules/neo-async": {
+ "version": "2.6.2",
+ "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@sap/cds-dk/node_modules/node-abi": {
+ "version": "3.79.0",
+ "integrity": "sha512-Pr/5KdBQGG8TirdkS0qN3B+f3eo8zTOfZQWAxHoJqopMz2/uvRnG+S4fWu/6AZxKei2CP2p/psdQ5HFC2Ap5BA==",
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "dependencies": {
+ "semver": "^7.3.5"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/@sap/cds-dk/node_modules/node-cache": {
+ "version": "5.1.2",
+ "integrity": "sha512-t1QzWwnk4sjLWaQAS8CHgOJ+RAfmHpxFWmc36IWTiWHQfs0w5JDMBS1b1ZxQteo0vVVuWJvIUKHDkkeK7vIGCg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "clone": "2.x"
+ },
+ "engines": {
+ "node": ">= 8.0.0"
+ }
+ },
+ "node_modules/@sap/cds-dk/node_modules/node-watch": {
+ "version": "0.7.4",
+ "integrity": "sha512-RinNxoz4W1cep1b928fuFhvAQ5ag/+1UlMDV7rbyGthBIgsiEouS4kvRayvvboxii4m8eolKOIBo3OjDqbc+uQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/@sap/cds-dk/node_modules/object-inspect": {
+ "version": "1.13.4",
+ "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/@sap/cds-dk/node_modules/on-finished": {
+ "version": "2.4.1",
+ "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "ee-first": "1.1.1"
+ },
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/@sap/cds-dk/node_modules/once": {
+ "version": "1.4.0",
+ "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==",
+ "dev": true,
+ "license": "ISC",
+ "optional": true,
+ "dependencies": {
+ "wrappy": "1"
+ }
+ },
+ "node_modules/@sap/cds-dk/node_modules/parseurl": {
+ "version": "1.3.3",
+ "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/@sap/cds-dk/node_modules/path-to-regexp": {
+ "version": "0.1.12",
+ "integrity": "sha512-RA1GjUVMnvYFxuqovrEqZoxxW5NUZqbwKtYz/Tt7nXerk0LbLblQmrsgdeOxV5SFHf0UDggjS/bSeOZwt1pmEQ==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@sap/cds-dk/node_modules/pluralize": {
+ "version": "8.0.0",
+ "integrity": "sha512-Nc3IT5yHzflTfbjgqWcCPpo7DaKy4FnpB0l/zCAW0Tc7jxAiuqSxHasntB3D7887LSrA93kDJ9IXovxJYxyLCA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/@sap/cds-dk/node_modules/prebuild-install": {
+ "version": "7.1.3",
+ "integrity": "sha512-8Mf2cbV7x1cXPUILADGI3wuhfqWvtiLA1iclTDbFRZkgRQS0NqsPZphna9V+HyTEadheuPmjaJMsbzKQFOzLug==",
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "dependencies": {
+ "detect-libc": "^2.0.0",
+ "expand-template": "^2.0.3",
+ "github-from-package": "0.0.0",
+ "minimist": "^1.2.3",
+ "mkdirp-classic": "^0.5.3",
+ "napi-build-utils": "^2.0.0",
+ "node-abi": "^3.3.0",
+ "pump": "^3.0.0",
+ "rc": "^1.2.7",
+ "simple-get": "^4.0.0",
+ "tar-fs": "^2.0.0",
+ "tunnel-agent": "^0.6.0"
+ },
+ "bin": {
+ "prebuild-install": "bin.js"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/@sap/cds-dk/node_modules/proxy-addr": {
+ "version": "2.0.7",
+ "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "forwarded": "0.2.0",
+ "ipaddr.js": "1.9.1"
+ },
+ "engines": {
+ "node": ">= 0.10"
+ }
+ },
+ "node_modules/@sap/cds-dk/node_modules/proxy-from-env": {
+ "version": "1.1.0",
+ "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@sap/cds-dk/node_modules/pump": {
+ "version": "3.0.3",
+ "integrity": "sha512-todwxLMY7/heScKmntwQG8CXVkWUOdYxIvY2s0VWAAMh/nd8SoYiRaKjlr7+iCs984f2P8zvrfWcDDYVb73NfA==",
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "dependencies": {
+ "end-of-stream": "^1.1.0",
+ "once": "^1.3.1"
+ }
+ },
+ "node_modules/@sap/cds-dk/node_modules/qs": {
+ "version": "6.13.0",
+ "integrity": "sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg==",
+ "dev": true,
+ "license": "BSD-3-Clause",
+ "dependencies": {
+ "side-channel": "^1.0.6"
+ },
+ "engines": {
+ "node": ">=0.6"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/@sap/cds-dk/node_modules/range-parser": {
+ "version": "1.2.1",
+ "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/@sap/cds-dk/node_modules/raw-body": {
+ "version": "2.5.2",
+ "integrity": "sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "bytes": "3.1.2",
+ "http-errors": "2.0.0",
+ "iconv-lite": "0.4.24",
+ "unpipe": "1.0.0"
+ },
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/@sap/cds-dk/node_modules/rc": {
+ "version": "1.2.8",
+ "integrity": "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==",
+ "dev": true,
+ "license": "(BSD-2-Clause OR MIT OR Apache-2.0)",
+ "optional": true,
+ "dependencies": {
+ "deep-extend": "^0.6.0",
+ "ini": "~1.3.0",
+ "minimist": "^1.2.0",
+ "strip-json-comments": "~2.0.1"
+ },
+ "bin": {
+ "rc": "cli.js"
+ }
+ },
+ "node_modules/@sap/cds-dk/node_modules/readable-stream": {
+ "version": "3.6.2",
+ "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==",
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "dependencies": {
+ "inherits": "^2.0.3",
+ "string_decoder": "^1.1.1",
+ "util-deprecate": "^1.0.1"
+ },
+ "engines": {
+ "node": ">= 6"
+ }
+ },
+ "node_modules/@sap/cds-dk/node_modules/safe-buffer": {
+ "version": "5.2.1",
+ "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/feross"
+ },
+ {
+ "type": "patreon",
+ "url": "https://www.patreon.com/feross"
+ },
+ {
+ "type": "consulting",
+ "url": "https://feross.org/support"
+ }
+ ],
+ "license": "MIT"
+ },
+ "node_modules/@sap/cds-dk/node_modules/safer-buffer": {
+ "version": "2.1.2",
+ "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@sap/cds-dk/node_modules/sax": {
+ "version": "1.4.1",
+ "integrity": "sha512-+aWOz7yVScEGoKNd4PA10LZ8sk0A/z5+nXQG5giUO5rprX9jgYsTdov9qCchZiPIZezbZH+jRut8nPodFAX4Jg==",
+ "dev": true,
+ "license": "ISC"
+ },
+ "node_modules/@sap/cds-dk/node_modules/semver": {
+ "version": "7.7.3",
+ "integrity": "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==",
+ "dev": true,
+ "license": "ISC",
+ "optional": true,
+ "bin": {
+ "semver": "bin/semver.js"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/@sap/cds-dk/node_modules/send": {
+ "version": "0.19.0",
+ "integrity": "sha512-dW41u5VfLXu8SJh5bwRmyYUbAoSB3c9uQh6L8h/KtsFREPWpbX1lrljJo186Jc4nmci/sGUZ9a0a0J2zgfq2hw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "debug": "2.6.9",
+ "depd": "2.0.0",
+ "destroy": "1.2.0",
+ "encodeurl": "~1.0.2",
+ "escape-html": "~1.0.3",
+ "etag": "~1.8.1",
+ "fresh": "0.5.2",
+ "http-errors": "2.0.0",
+ "mime": "1.6.0",
+ "ms": "2.1.3",
+ "on-finished": "2.4.1",
+ "range-parser": "~1.2.1",
+ "statuses": "2.0.1"
+ },
+ "engines": {
+ "node": ">= 0.8.0"
+ }
+ },
+ "node_modules/@sap/cds-dk/node_modules/send/node_modules/debug": {
+ "version": "2.6.9",
+ "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "ms": "2.0.0"
+ }
+ },
+ "node_modules/@sap/cds-dk/node_modules/send/node_modules/debug/node_modules/ms": {
+ "version": "2.0.0",
+ "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@sap/cds-dk/node_modules/send/node_modules/encodeurl": {
+ "version": "1.0.2",
+ "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/@sap/cds-dk/node_modules/serve-static": {
+ "version": "1.16.2",
+ "integrity": "sha512-VqpjJZKadQB/PEbEwvFdO43Ax5dFBZ2UECszz8bQ7pi7wt//PWe1P6MN7eCnjsatYtBT6EuiClbjSWP2WrIoTw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "encodeurl": "~2.0.0",
+ "escape-html": "~1.0.3",
+ "parseurl": "~1.3.3",
+ "send": "0.19.0"
+ },
+ "engines": {
+ "node": ">= 0.8.0"
+ }
+ },
+ "node_modules/@sap/cds-dk/node_modules/setprototypeof": {
+ "version": "1.2.0",
+ "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==",
+ "dev": true,
+ "license": "ISC"
+ },
+ "node_modules/@sap/cds-dk/node_modules/side-channel": {
+ "version": "1.1.0",
+ "integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "es-errors": "^1.3.0",
+ "object-inspect": "^1.13.3",
+ "side-channel-list": "^1.0.0",
+ "side-channel-map": "^1.0.1",
+ "side-channel-weakmap": "^1.0.2"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/@sap/cds-dk/node_modules/side-channel-list": {
+ "version": "1.0.0",
+ "integrity": "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "es-errors": "^1.3.0",
+ "object-inspect": "^1.13.3"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/@sap/cds-dk/node_modules/side-channel-map": {
+ "version": "1.0.1",
+ "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "call-bound": "^1.0.2",
+ "es-errors": "^1.3.0",
+ "get-intrinsic": "^1.2.5",
+ "object-inspect": "^1.13.3"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/@sap/cds-dk/node_modules/side-channel-weakmap": {
+ "version": "1.0.2",
+ "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "call-bound": "^1.0.2",
+ "es-errors": "^1.3.0",
+ "get-intrinsic": "^1.2.5",
+ "object-inspect": "^1.13.3",
+ "side-channel-map": "^1.0.1"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/@sap/cds-dk/node_modules/simple-concat": {
+ "version": "1.0.1",
+ "integrity": "sha512-cSFtAPtRhljv69IK0hTVZQ+OfE9nePi/rtJmw5UjHeVyVroEqJXP1sFztKUy1qU+xvz3u/sfYJLa947b7nAN2Q==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/feross"
+ },
+ {
+ "type": "patreon",
+ "url": "https://www.patreon.com/feross"
+ },
+ {
+ "type": "consulting",
+ "url": "https://feross.org/support"
+ }
+ ],
+ "license": "MIT",
+ "optional": true
+ },
+ "node_modules/@sap/cds-dk/node_modules/simple-get": {
+ "version": "4.0.1",
+ "integrity": "sha512-brv7p5WgH0jmQJr1ZDDfKDOSeWWg+OVypG99A/5vYGPqJ6pxiaHLy8nxtFjBA7oMa01ebA9gfh1uMCFqOuXxvA==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/feross"
+ },
+ {
+ "type": "patreon",
+ "url": "https://www.patreon.com/feross"
+ },
+ {
+ "type": "consulting",
+ "url": "https://feross.org/support"
+ }
+ ],
+ "license": "MIT",
+ "optional": true,
+ "dependencies": {
+ "decompress-response": "^6.0.0",
+ "once": "^1.3.1",
+ "simple-concat": "^1.0.0"
+ }
+ },
+ "node_modules/@sap/cds-dk/node_modules/source-map": {
+ "version": "0.6.1",
+ "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
+ "dev": true,
+ "license": "BSD-3-Clause",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/@sap/cds-dk/node_modules/statuses": {
+ "version": "2.0.1",
+ "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/@sap/cds-dk/node_modules/string_decoder": {
+ "version": "1.3.0",
+ "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==",
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "dependencies": {
+ "safe-buffer": "~5.2.0"
+ }
+ },
+ "node_modules/@sap/cds-dk/node_modules/strip-json-comments": {
+ "version": "2.0.1",
+ "integrity": "sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ==",
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/@sap/cds-dk/node_modules/tar-fs": {
+ "version": "2.1.4",
+ "integrity": "sha512-mDAjwmZdh7LTT6pNleZ05Yt65HC3E+NiQzl672vQG38jIrehtJk/J3mNwIg+vShQPcLF/LV7CMnDW6vjj6sfYQ==",
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "dependencies": {
+ "chownr": "^1.1.1",
+ "mkdirp-classic": "^0.5.2",
+ "pump": "^3.0.0",
+ "tar-stream": "^2.1.4"
+ }
+ },
+ "node_modules/@sap/cds-dk/node_modules/tar-stream": {
+ "version": "2.2.0",
+ "integrity": "sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==",
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "dependencies": {
+ "bl": "^4.0.3",
+ "end-of-stream": "^1.4.1",
+ "fs-constants": "^1.0.0",
+ "inherits": "^2.0.3",
+ "readable-stream": "^3.1.1"
+ },
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/@sap/cds-dk/node_modules/to-regex-range": {
+ "version": "5.0.1",
+ "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "is-number": "^7.0.0"
+ },
+ "engines": {
+ "node": ">=8.0"
+ }
+ },
+ "node_modules/@sap/cds-dk/node_modules/toidentifier": {
+ "version": "1.0.1",
+ "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.6"
+ }
+ },
+ "node_modules/@sap/cds-dk/node_modules/tunnel-agent": {
+ "version": "0.6.0",
+ "integrity": "sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "optional": true,
+ "dependencies": {
+ "safe-buffer": "^5.0.1"
+ },
+ "engines": {
+ "node": "*"
+ }
+ },
+ "node_modules/@sap/cds-dk/node_modules/type-is": {
+ "version": "1.6.18",
+ "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "media-typer": "0.3.0",
+ "mime-types": "~2.1.24"
+ },
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/@sap/cds-dk/node_modules/uglify-js": {
+ "version": "3.19.3",
+ "integrity": "sha512-v3Xu+yuwBXisp6QYTcH4UbH+xYJXqnq2m/LtQVWKWzYc1iehYnLixoQDN9FH6/j9/oybfd6W9Ghwkl8+UMKTKQ==",
+ "dev": true,
+ "license": "BSD-2-Clause",
+ "optional": true,
+ "bin": {
+ "uglifyjs": "bin/uglifyjs"
+ },
+ "engines": {
+ "node": ">=0.8.0"
+ }
+ },
+ "node_modules/@sap/cds-dk/node_modules/unpipe": {
+ "version": "1.0.0",
+ "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/@sap/cds-dk/node_modules/util-deprecate": {
+ "version": "1.0.2",
+ "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==",
+ "dev": true,
+ "license": "MIT",
+ "optional": true
+ },
+ "node_modules/@sap/cds-dk/node_modules/utils-merge": {
+ "version": "1.0.1",
+ "integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.4.0"
+ }
+ },
+ "node_modules/@sap/cds-dk/node_modules/vary": {
+ "version": "1.1.2",
+ "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/@sap/cds-dk/node_modules/verror": {
+ "version": "1.10.1",
+ "integrity": "sha512-veufcmxri4e3XSrT0xwfUR7kguIkaxBeosDg00yDWhk49wdwkSUrvvsm7nc75e1PUyvIeZj6nS8VQRYz2/S4Xg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "assert-plus": "^1.0.0",
+ "core-util-is": "1.0.2",
+ "extsprintf": "^1.2.0"
+ },
+ "engines": {
+ "node": ">=0.6.0"
+ }
+ },
+ "node_modules/@sap/cds-dk/node_modules/wordwrap": {
+ "version": "1.0.0",
+ "integrity": "sha512-gvVzJFlPycKc5dZN4yPkP8w7Dc37BtP1yczEneOb4uq34pXZcvrtRTmWV8W+Ume+XCxKgbjM+nevkyFPMybd4Q==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@sap/cds-dk/node_modules/wrappy": {
+ "version": "1.0.2",
+ "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==",
+ "dev": true,
+ "license": "ISC",
+ "optional": true
+ },
+ "node_modules/@sap/cds-dk/node_modules/ws": {
+ "version": "8.18.3",
+ "integrity": "sha512-PEIGCY5tSlUt50cqyMXfCzX+oOPqN0vuGqWzbcJ2xvnkzkq46oOpz7dQaTDBdfICb4N14+GARUDw2XV2N4tvzg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=10.0.0"
+ },
+ "peerDependencies": {
+ "bufferutil": "^4.0.1",
+ "utf-8-validate": ">=5.0.2"
+ },
+ "peerDependenciesMeta": {
+ "bufferutil": {
+ "optional": true
+ },
+ "utf-8-validate": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@sap/cds-dk/node_modules/xml-js": {
+ "version": "1.6.11",
+ "integrity": "sha512-7rVi2KMfwfWFl+GpPg6m80IVMWXLRjO+PxTq7V2CDhoGak0wzYzFgUY2m4XJ47OGdXd8eLE8EmwfAmdjw7lC1g==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "sax": "^1.2.4"
+ },
+ "bin": {
+ "xml-js": "bin/cli.js"
+ }
+ },
+ "node_modules/@sap/cds-dk/node_modules/yaml": {
+ "version": "2.8.1",
+ "integrity": "sha512-lcYcMxX2PO9XMGvAJkJ3OsNMw+/7FKes7/hgerGUYWIoWu5j/+YQqcZr5JnPZWzOsEBgMbSbiSTn/dv/69Mkpw==",
+ "dev": true,
+ "license": "ISC",
+ "bin": {
+ "yaml": "bin.mjs"
+ },
+ "engines": {
+ "node": ">= 14.6"
+ }
+ }
+ }
+}
diff --git a/samples/bookshop/package.json b/samples/bookshop/package.json
new file mode 100644
index 000000000..546722325
--- /dev/null
+++ b/samples/bookshop/package.json
@@ -0,0 +1,10 @@
+{
+ "name": "bookshop-cds",
+ "version": "1.0.0",
+ "description": "Generated by cds-services-archetype",
+ "license": "ISC",
+ "repository": "",
+ "devDependencies": {
+ "@sap/cds-dk": "^9.3.2"
+ }
+}
diff --git a/samples/bookshop/pom.xml b/samples/bookshop/pom.xml
new file mode 100644
index 000000000..5a471ad59
--- /dev/null
+++ b/samples/bookshop/pom.xml
@@ -0,0 +1,189 @@
+
+
+ 4.0.0
+
+ customer
+ bookshop-parent
+ ${revision}
+ pom
+
+ bookshop parent
+
+
+
+ 1.0.0-SNAPSHOT
+
+
+ 17
+ 4.4.0
+ 3.5.6
+
+ https://nodejs.org/dist/
+ UTF-8
+
+
+
+ srv
+
+
+
+
+
+
+ com.sap.cds
+ cds-services-bom
+ ${cds.services.version}
+ pom
+ import
+
+
+
+
+ org.springframework.boot
+ spring-boot-dependencies
+ ${spring.boot.version}
+ pom
+ import
+
+
+
+
+ com.sap.cds
+ cds-feature-attachments
+ [1.0.0,2.0.0)
+
+
+
+
+
+
+
+
+
+ com.sap.cds
+ cds-maven-plugin
+ ${cds.services.version}
+
+
+ cds.resolve
+
+ resolve
+
+
+
+
+
+
+
+
+
+
+ maven-compiler-plugin
+ 3.14.1
+
+ ${jdk.version}
+ UTF-8
+
+
+
+
+
+ org.springframework.boot
+ spring-boot-maven-plugin
+ ${spring.boot.version}
+
+ true
+
+
+
+
+
+ maven-surefire-plugin
+ 3.5.4
+
+
+
+
+ org.codehaus.mojo
+ flatten-maven-plugin
+ 1.7.3
+
+ true
+ resolveCiFriendliesOnly
+
+
+
+ flatten
+ process-resources
+
+ flatten
+
+
+
+ flatten.clean
+ clean
+
+ clean
+
+
+
+
+
+
+
+ maven-enforcer-plugin
+ 3.6.2
+
+
+ Project Structure Checks
+
+ enforce
+
+
+
+
+ 3.6.3
+
+
+ ${jdk.version}
+
+
+
+ true
+
+
+
+
+
+
+
+ org.jacoco
+ jacoco-maven-plugin
+ 0.8.14
+
+
+ **/gen/**
+ **/generated/**
+
+
+
+
+ jacoco-initialize
+
+ prepare-agent
+
+
+
+ jacoco-report
+ test
+
+ report
+
+
+
+
+
+
+
diff --git a/samples/bookshop/srv/admin-service.cds b/samples/bookshop/srv/admin-service.cds
new file mode 100644
index 000000000..9ae8bbc10
--- /dev/null
+++ b/samples/bookshop/srv/admin-service.cds
@@ -0,0 +1,6 @@
+using {sap.capire.bookshop as my} from '../db/schema';
+
+service AdminService @(requires: 'admin') {
+ entity Books as projection on my.Books;
+ entity Authors as projection on my.Authors;
+}
diff --git a/samples/bookshop/srv/attachments.cds b/samples/bookshop/srv/attachments.cds
new file mode 100644
index 000000000..30db69511
--- /dev/null
+++ b/samples/bookshop/srv/attachments.cds
@@ -0,0 +1,28 @@
+using {sap.capire.bookshop as my} from '../db/schema';
+using {sap.attachments.Attachments} from 'com.sap.cds/cds-feature-attachments';
+
+// Extend Books entity to support file attachments (images, PDFs, documents)
+// Each book can have multiple attachments via composition relationship
+extend my.Books with {
+ attachments : Composition of many Attachments;
+}
+
+// Add UI component for attachments table to the Browse Books App
+using {CatalogService as service} from '../app/services';
+
+annotate service.Books with @(UI.Facets: [{
+ $Type : 'UI.ReferenceFacet',
+ ID : 'AttachmentsFacet',
+ Label : '{i18n>attachments}',
+ Target: 'attachments/@UI.LineItem'
+}]);
+
+// Adding the UI Component (a table) to the Administrator App
+using {AdminService as adminService} from '../app/services';
+
+annotate adminService.Books with @(UI.Facets: [{
+ $Type : 'UI.ReferenceFacet',
+ ID : 'AttachmentsFacet',
+ Label : '{i18n>attachments}',
+ Target: 'attachments/@UI.LineItem'
+}]);
diff --git a/samples/bookshop/srv/cat-service.cds b/samples/bookshop/srv/cat-service.cds
new file mode 100644
index 000000000..1d2cbbab8
--- /dev/null
+++ b/samples/bookshop/srv/cat-service.cds
@@ -0,0 +1,34 @@
+using {sap.capire.bookshop as my} from '../db/schema';
+
+service CatalogService {
+
+ /** For displaying lists of Books */
+ @readonly
+ entity ListOfBooks as
+ projection on Books
+ excluding {
+ descr
+ };
+
+ /** For display in details pages */
+ @readonly
+ entity Books as
+ projection on my.Books {
+ *,
+ author.name as author
+ }
+ excluding {
+ createdBy,
+ modifiedBy
+ };
+
+ action submitOrder(book : Books:ID, quantity : Integer) returns {
+ stock : Integer
+ };
+
+ event OrderedBook : {
+ book : Books:ID;
+ quantity : Integer;
+ buyer : String
+ };
+}
diff --git a/samples/bookshop/srv/pom.xml b/samples/bookshop/srv/pom.xml
new file mode 100644
index 000000000..74c7054b1
--- /dev/null
+++ b/samples/bookshop/srv/pom.xml
@@ -0,0 +1,154 @@
+
+
+ 4.0.0
+
+
+ bookshop-parent
+ customer
+ ${revision}
+
+
+ bookshop
+ jar
+
+ bookshop
+
+
+
+
+
+ com.sap.cds
+ cds-starter-spring-boot
+
+
+
+ org.springframework.boot
+ spring-boot-devtools
+ true
+
+
+
+ org.springframework.boot
+ spring-boot-starter-test
+ test
+
+
+
+ com.sap.cds
+ cds-adapter-odata-v4
+ runtime
+
+
+
+ com.h2database
+ h2
+ runtime
+
+
+
+ org.springframework.boot
+ spring-boot-starter-security
+
+
+
+
+ com.sap.cds
+ cds-feature-attachments
+
+
+
+
+ ${project.artifactId}
+
+
+
+ org.springframework.boot
+ spring-boot-maven-plugin
+ ${spring.boot.version}
+
+ false
+
+
+
+ repackage
+
+ repackage
+
+
+ exec
+
+
+
+
+
+
+
+ com.sap.cds
+ cds-maven-plugin
+
+
+ cds.clean
+
+ clean
+
+
+
+
+ cds.install-node
+
+ install-node
+
+
+
+
+ cds.npm-ci
+
+ npm
+
+
+ ci
+
+
+
+
+ cds.resolve
+
+ resolve
+
+
+
+
+ cds.build
+
+ cds
+
+
+
+ build --for java
+ deploy --to h2 --with-mocks --dry --out
+ "${project.basedir}/src/main/resources/schema-h2.sql"
+
+
+
+
+
+ cds.generate
+
+ generate
+
+
+ cds.gen
+ true
+ true
+ true
+ true
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/samples/bookshop/srv/src/main/java/customer/bookshop/Application.java b/samples/bookshop/srv/src/main/java/customer/bookshop/Application.java
new file mode 100644
index 000000000..f395d210b
--- /dev/null
+++ b/samples/bookshop/srv/src/main/java/customer/bookshop/Application.java
@@ -0,0 +1,13 @@
+package customer.bookshop;
+
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+
+@SpringBootApplication
+public class Application {
+
+ public static void main(String[] args) {
+ SpringApplication.run(Application.class, args);
+ }
+
+}
diff --git a/samples/bookshop/srv/src/main/java/customer/bookshop/handlers/CatalogServiceHandler.java b/samples/bookshop/srv/src/main/java/customer/bookshop/handlers/CatalogServiceHandler.java
new file mode 100644
index 000000000..c480672e3
--- /dev/null
+++ b/samples/bookshop/srv/src/main/java/customer/bookshop/handlers/CatalogServiceHandler.java
@@ -0,0 +1,63 @@
+package customer.bookshop.handlers;
+
+import java.util.stream.Stream;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Component;
+
+import com.sap.cds.ql.Select;
+import com.sap.cds.ql.Update;
+import com.sap.cds.services.cds.CqnService;
+import com.sap.cds.services.handler.EventHandler;
+import com.sap.cds.services.handler.annotations.After;
+import com.sap.cds.services.handler.annotations.On;
+import com.sap.cds.services.handler.annotations.ServiceName;
+import com.sap.cds.services.persistence.PersistenceService;
+
+import cds.gen.catalogservice.Books;
+import cds.gen.catalogservice.Books_;
+import cds.gen.catalogservice.CatalogService_;
+import cds.gen.catalogservice.OrderedBook;
+import cds.gen.catalogservice.OrderedBookContext;
+import cds.gen.catalogservice.SubmitOrderContext;
+import cds.gen.catalogservice.SubmitOrderContext.ReturnType;
+
+@Component
+@ServiceName(CatalogService_.CDS_NAME)
+public class CatalogServiceHandler implements EventHandler {
+
+ @Autowired
+ private PersistenceService db;
+
+ @On
+ public void submitOrder(SubmitOrderContext context) {
+ // decrease and update stock in database
+ db.run(Update.entity(Books_.class).byId(context.getBook()).set(b -> b.stock(), s -> s.minus(context.getQuantity())));
+
+ // read new stock from database
+ Books book = db.run(Select.from(Books_.class).where(b -> b.ID().eq(context.getBook()))).single(Books.class);
+
+ // return new stock to client
+ ReturnType result = SubmitOrderContext.ReturnType.create();
+ result.setStock(book.getStock());
+
+ OrderedBook orderedBook = OrderedBook.create();
+ orderedBook.setBook(book.getId());
+ orderedBook.setQuantity(context.getQuantity());
+ orderedBook.setBuyer(context.getUserInfo().getName());
+
+ OrderedBookContext orderedBookEvent = OrderedBookContext.create();
+ orderedBookEvent.setData(orderedBook);
+ context.getService().emit(orderedBookEvent);
+
+ context.setResult(result);
+ }
+
+ @After(event = CqnService.EVENT_READ)
+ public void discountBooks(Stream books) {
+ books.filter(b -> b.getTitle() != null && b.getStock() != null)
+ .filter(b -> b.getStock() > 200)
+ .forEach(b -> b.setTitle(b.getTitle() + " (discounted)"));
+ }
+
+}
\ No newline at end of file
diff --git a/samples/bookshop/srv/src/main/resources/application.yaml b/samples/bookshop/srv/src/main/resources/application.yaml
new file mode 100644
index 000000000..1ee2ae9f4
--- /dev/null
+++ b/samples/bookshop/srv/src/main/resources/application.yaml
@@ -0,0 +1,22 @@
+
+---
+spring:
+ config:
+ activate:
+ on-profile: default
+ sql:
+ init:
+ platform: h2
+cds:
+ security:
+ mock:
+ users:
+ admin:
+ password: admin
+ roles:
+ - admin
+ user:
+ password: user
+ data-source:
+ auto-config:
+ enabled: false
diff --git a/integration-tests/srv/src/test/java/com/sap/cds/feature/attachments/integrationtests/ApplicationTest.java b/samples/bookshop/srv/src/test/java/customer/bookshop/ApplicationTest.java
similarity index 78%
rename from integration-tests/srv/src/test/java/com/sap/cds/feature/attachments/integrationtests/ApplicationTest.java
rename to samples/bookshop/srv/src/test/java/customer/bookshop/ApplicationTest.java
index 61f1b5342..152543a5b 100644
--- a/integration-tests/srv/src/test/java/com/sap/cds/feature/attachments/integrationtests/ApplicationTest.java
+++ b/samples/bookshop/srv/src/test/java/customer/bookshop/ApplicationTest.java
@@ -1,7 +1,4 @@
-/*
- * © 2024-2024 SAP SE or an SAP affiliate company and cds-feature-attachments contributors.
- */
-package com.sap.cds.feature.attachments.integrationtests;
+package customer.bookshop;
import static org.assertj.core.api.Assertions.assertThat;
@@ -21,4 +18,4 @@ class ApplicationTest {
void checkApplicationContextCanBeLoaded() {
assertThat(context).isNotNull();
}
-}
+}
\ No newline at end of file
diff --git a/samples/bookshop/srv/src/test/java/customer/bookshop/handlers/CatalogServiceHandlerTest.java b/samples/bookshop/srv/src/test/java/customer/bookshop/handlers/CatalogServiceHandlerTest.java
new file mode 100644
index 000000000..e008b5ad6
--- /dev/null
+++ b/samples/bookshop/srv/src/test/java/customer/bookshop/handlers/CatalogServiceHandlerTest.java
@@ -0,0 +1,159 @@
+package customer.bookshop.handlers;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import java.util.stream.Stream;
+
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.mockito.InjectMocks;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import com.sap.cds.Result;
+import com.sap.cds.ql.Select;
+import com.sap.cds.ql.Update;
+import com.sap.cds.services.persistence.PersistenceService;
+import com.sap.cds.services.request.UserInfo;
+
+import cds.gen.catalogservice.Books;
+import cds.gen.catalogservice.CatalogService;
+import cds.gen.catalogservice.OrderedBookContext;
+import cds.gen.catalogservice.SubmitOrderContext;
+
+class CatalogServiceHandlerTest {
+
+ @Mock
+ private PersistenceService db;
+
+ @InjectMocks
+ private CatalogServiceHandler handler;
+
+ private Books book = Books.create();
+
+ @BeforeEach
+ public void setUp() {
+ MockitoAnnotations.openMocks(this);
+ book.setTitle("title");
+ }
+
+ @Test
+ void testDiscount() {
+ book.setStock(500);
+ handler.discountBooks(Stream.of(book));
+ assertEquals("title (discounted)", book.getTitle());
+ }
+
+ @Test
+ void testNoDiscount() {
+ book.setStock(100);
+ handler.discountBooks(Stream.of(book));
+ assertEquals("title", book.getTitle());
+ }
+
+ @Test
+ void testNoStockAvailable() {
+ handler.discountBooks(Stream.of(book));
+ assertEquals("title", book.getTitle());
+ }
+
+ @Test
+ void testDiscountWithNullTitle() {
+ book.setTitle(null);
+ book.setStock(500);
+ handler.discountBooks(Stream.of(book));
+ // Should not throw exception and title should remain null
+ assertEquals(null, book.getTitle());
+ }
+
+ @Test
+ void testDiscountWithNullStock() {
+ book.setTitle("test");
+ book.setStock(null);
+ handler.discountBooks(Stream.of(book));
+ // Should not throw exception and title should remain unchanged
+ assertEquals("test", book.getTitle());
+ }
+
+ @Test
+ void testSubmitOrder() {
+ // Setup
+ String bookId = "aeeda49f-72f2-4880-be27-a513b2e53040";
+ Integer quantity = 2;
+ Integer expectedNewStock = 9;
+ String userName = "testuser";
+
+ // Mock the context
+ SubmitOrderContext context = mock(SubmitOrderContext.class);
+ UserInfo userInfo = mock(UserInfo.class);
+
+ // Import the generated service interface
+ CatalogService catalogService = mock(CatalogService.class);
+
+ when(context.getBook()).thenReturn(bookId);
+ when(context.getQuantity()).thenReturn(quantity);
+ when(context.getUserInfo()).thenReturn(userInfo);
+ when(context.getService()).thenReturn(catalogService);
+ when(userInfo.getName()).thenReturn(userName);
+
+ // Mock the book result after update
+ Books updatedBook = Books.create();
+ updatedBook.setId(bookId);
+ updatedBook.setStock(expectedNewStock);
+
+ Result mockResult = mock(Result.class);
+ when(mockResult.single(Books.class)).thenReturn(updatedBook);
+ when(db.run(any(Select.class))).thenReturn(mockResult);
+
+ // Execute
+ handler.submitOrder(context);
+
+ // Verify database operations
+ verify(db).run(any(Update.class));
+ verify(db).run(any(Select.class));
+ verify(context).setResult(any(SubmitOrderContext.ReturnType.class));
+ verify(catalogService).emit(any(OrderedBookContext.class));
+ }
+
+ @Test
+ void testSubmitOrderWithZeroQuantity() {
+ // Setup
+ String bookId = "book-123";
+ Integer quantity = 0;
+ Integer currentStock = 100;
+ String userName = "testuser";
+
+ // Mock the context
+ SubmitOrderContext context = mock(SubmitOrderContext.class);
+ UserInfo userInfo = mock(UserInfo.class);
+
+ CatalogService catalogService = mock(CatalogService.class);
+
+ when(context.getBook()).thenReturn(bookId);
+ when(context.getQuantity()).thenReturn(quantity);
+ when(context.getUserInfo()).thenReturn(userInfo);
+ when(context.getService()).thenReturn(catalogService);
+ when(userInfo.getName()).thenReturn(userName);
+
+ // Mock the book result (stock should remain the same)
+ Books updatedBook = Books.create();
+ updatedBook.setId(bookId);
+ updatedBook.setStock(currentStock);
+
+ Result mockResult = mock(Result.class);
+ when(mockResult.single(Books.class)).thenReturn(updatedBook);
+ when(db.run(any(Select.class))).thenReturn(mockResult);
+
+ // Execute
+ handler.submitOrder(context);
+
+ // Verify database operations still happen
+ verify(db).run(any(Update.class));
+ verify(db).run(any(Select.class));
+ verify(context).setResult(any(SubmitOrderContext.ReturnType.class));
+ }
+}