diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index a1ff0a9a3..06e3b5d06 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -1,3 +1,3 @@ # This files defines code ownership. -* @renejeglinsky @manjuX +* @renejeglinsky @smahati diff --git a/.github/etc/create-review.cjs b/.github/etc/create-review.cjs new file mode 100644 index 000000000..d282376e1 --- /dev/null +++ b/.github/etc/create-review.cjs @@ -0,0 +1,244 @@ +const cspellRegExp = /^(.*\.md)(:\d+:?\d*)\s*- Unknown word \((.*?)\)\s+-- (.*?) Suggestions: (\[.*\])$/ +const markdownlintRegExp = /^(.*\.md)(:\d+:?\d*) (MD\d+)\/\S+\s+(.+) \[(.*:)?\s*(.*)\]$/ + +const createSuggestionText = (suggestion) => '```suggestion\n' + suggestion + '\n```\n' + +const createCspellSuggestionText = (suggestion, other) => createSuggestionText(suggestion) + `Or maybe one of these: ${other.map(el => `**${el}**`).join(', ')}?` + +const createWordsWithoutSuggestionsText = (words) => `For the following words no suggestions could be found, consider adding them to the word list:\n${words.map(word => `* ${word}\n`).join('')}` + +const createUnknownWordComment = (word) => `Fix the spelling mistake in "**${word}**" or add it to the **project-words.txt** list.` + +const createMissingCodeFencesText = (lines) => +` +\`\`\`\`suggestion +${lines.join('\n')} +\`\`\`\` + +Please add a language tag. For plain text add \`txt\` as language tag. +` + +const getNoEmptyLinkText = () => 'No empty links. Please provide a link value.' + +const getSpellingCorrectionTip = () => +` +Generally, for each spelling mistake there are 2 ways to fix it: +1. Fix the spelling mistake and commit it. +2. The word is incorrectly reported as misspelled → put the word on the **project-words.txt** list, located in the root project directory. +` + +module.exports = async ({ github, require, exec, core }) => { + const { readFileSync, existsSync } = require('fs') + const { join } = require('path') + const { SHA, BASE_DIR, BASE_SHA, PULL_NUMBER, HEAD_SHA, REPO, REPO_OWNER } = process.env + + const cspellLogFile = join(BASE_DIR, 'CSPELL.log') + const markdownlintLogFile = join(BASE_DIR, 'MARKDOWNLINT.log') + + const comments = [] + let body = '' + let lintErrorsText = '' + let spellingMistakesText = '' + + if (existsSync(markdownlintLogFile)) { + const matches = readFileSync(markdownlintLogFile, 'utf-8') + .split('\n') + .filter(Boolean) + .map(line => line.replace(`${BASE_DIR}/`, '').match(markdownlintRegExp)) + + for(let [error, path, pointer, rule, description, contextKey = '', contextValue] of matches) { + + // MD011/no-reversed-links + if (rule === 'MD011') { + const { line, position } = await findPositionInDiff(contextValue, path) + + if (!line || position < 0) { + continue + } + + const [, link, text] = contextValue.match(/\((.*?)\)\[(.*?)\]/) + + const suggestion = line.replace(contextValue, `[${text}](${link})`).replace('+', '') + + const commentBody = createSuggestionText(suggestion) + + comments.push({ path, position, body: commentBody }) + } + + let context = `[${contextKey ? contextKey + ' ' : ''}${contextValue}]`.trim() + + // Rule MD042/no-empty-links + if (rule === 'MD042') { + context = context.replace(/(\[|\(|\]|\))/g, "\\$1") + + const emptyLink = contextValue.slice(1, -1) + + const { position } = await findPositionInDiff(emptyLink, path) + + if (position < 0) { + continue + } + + comments.push({ path, position, body: getNoEmptyLinkText() }) + } + + // Rule MD040/fenced-code-language + if (rule === 'MD040') { + context = '' + + const codeBlockLines = findCodeBlock(path, +pointer.slice(1)) + + const { start, end } = await findCodeBlockInDiff(codeBlockLines, path) + + if (start < 0 || end < 0) { + continue + } + + codeBlockLines[0] = codeBlockLines[0] + 'txt' + + comments.push({ path, body: createMissingCodeFencesText(codeBlockLines), start_line: start, line: end }) + } + + lintErrorsText += `* **${path}**${pointer} ${description} ${context}\n` + } + } + + if (existsSync(cspellLogFile)) { + let lines = readFileSync(cspellLogFile, 'utf-8').split('\n') + lines = Array.from({ length: lines.length / 2 }, (_el, idx) => lines[idx * 2] + lines[idx * 2 + 1].replace(/\t/g, '')) + + // we will create a review comment for each match + const matches = lines.map(line => line.replace(`${BASE_DIR}/`, '').match(cspellRegExp)) + + const wordsWithoutSuggestions = [] + + for (const [error, path, pointer , word, context, suggestionString] of matches) { + + // from "[s1, s2, s3]" to [ "s1", "s2", "s3" ] + const suggestions = suggestionString + .slice(1, -1) // remove brackets + .replace(/ /g, '') + .split(',') + .filter(Boolean) // remove empty strings + + const { line, position } = await findPositionInDiff(context, path) + + if (!line || position < 0) { + continue + } + + if (suggestions.length > 0) { + // replace word with first suggestions and remove first "+" sign + const suggestion = line.replace(word, suggestions[0]).replace('+', '') + + const commentBody = createCspellSuggestionText(suggestion, suggestions.slice(1)) + + comments.push({ path, position, body: commentBody }) + } else { + comments.push({ path, position, body: createUnknownWordComment(word) }) + + wordsWithoutSuggestions.push(word) + } + + spellingMistakesText += `* **${path}**${pointer} Unknown word "**${word}**"\n` + } + + if (wordsWithoutSuggestions.length > 0) { + spellingMistakesText += `\n${createWordsWithoutSuggestionsText(wordsWithoutSuggestions)}\n` + } + + if (matches.length > 0) { + spellingMistakesText += `${getSpellingCorrectionTip()}\n` + } + + } + + if (lintErrorsText) { + body += `Linting Errors\n---\n${lintErrorsText}` + } + + if (spellingMistakesText) { + body += `\nSpelling Mistakes\n---\n${spellingMistakesText}` + } + + if (body) { + await github.rest.pulls.createReview({ + owner: REPO_OWNER, + repo: REPO, + pull_number: PULL_NUMBER, + commit_id: HEAD_SHA, + body, + event: 'COMMENT', + comments + }) + } + + async function getDiff(file) { + let diff = '' + const opts = { + listeners: { + + stdout: (data) => { + diff += data.toString(); + } + }, + cwd: BASE_DIR + } + + await exec.exec(`git diff ${BASE_SHA} ${SHA} -- ${file}`, [], opts) + + return diff.split('\n') + } + + async function findPositionInDiff(context, file) { + const diff = await getDiff(file) + + const idxToStartingCoutingFrom = diff.findIndex(line => line.startsWith('@@')) + const idxOfLineToSearch = diff.findIndex(line => line.trim().startsWith('+') && line.replace(/ /g, '').includes(context.replace(/ /g, ''))) + + // context does not exist in diff --> errors is in file with diff, but errors was not introduced with current PR + if (idxToStartingCoutingFrom === -1 || idxOfLineToSearch === -1) { + return { position: -1 } + } + + const position = idxOfLineToSearch - idxToStartingCoutingFrom + + return { line: diff[idxOfLineToSearch], position } + } + + async function findCodeBlockInDiff(lines, file) { + const diff = await getDiff(file) + + let start = -1 + let end = -1 + for (let i = 0; i < diff.length; i++) { + for (let j = 0; j < lines.length; j++) { + if (diff[i + j].replace(/[-+]/, '') !== lines[j]) { + break + } + + if (j === lines.length - 1) { + start = i + end = i + j + } + } + } + + if (start === -1 || end === -1) { + return { start: -1, end: -1 } + } + + const idxToStartingCoutingFrom = diff.findIndex(line => line.startsWith('@@')) + + return { start: start - idxToStartingCoutingFrom, end: end - idxToStartingCoutingFrom } + } + + // startIdx starts at 1 + function findCodeBlock(file, startIdx) { + const lines = readFileSync(join(BASE_DIR, file), 'utf-8').split(/\n\r?/) + + const endIdx = lines.findIndex((el, idx) => idx >= startIdx && /`{3,}/.test(el.trim())) + + return lines.slice(startIdx - 1, endIdx + 1) + } +} diff --git a/.github/renovate.json b/.github/renovate.json index f37e4137c..bd56e47b3 100644 --- a/.github/renovate.json +++ b/.github/renovate.json @@ -1,7 +1,8 @@ { "$schema": "https://docs.renovatebot.com/renovate-schema.json", "extends": [ - "config:base" + "config:base", + ":disableDependencyDashboard" ], "includePaths": [ ".vitepress/config.ts", diff --git a/.github/workflows/PR-parent.yml b/.github/workflows/PR-SAP.yml similarity index 87% rename from .github/workflows/PR-parent.yml rename to .github/workflows/PR-SAP.yml index 3fcd57269..a3baff359 100644 --- a/.github/workflows/PR-parent.yml +++ b/.github/workflows/PR-SAP.yml @@ -1,18 +1,19 @@ -name: PR Parent +name: PR Build (SAP) on: pull_request: branches: [main] concurrency: - group: pr-parent-${{ github.workflow }}-${{ github.head_ref || github.run_id }} + group: pr-sap-${{ github.workflow }}-${{ github.head_ref || github.run_id }} cancel-in-progress: true jobs: - build: + build-sap: runs-on: ubuntu-latest + if: '! github.event.pull_request.head.repo.fork' steps: - - name: Checkout parent repo + - name: Checkout SAP repo run: | git config --global credential.helper "cache --timeout=3600" echo -e "url=https://user:${GH_TOKEN}@github.com\n" | git credential approve diff --git a/.github/workflows/PR.yml b/.github/workflows/PR.yml index a9036168c..8d5247602 100644 --- a/.github/workflows/PR.yml +++ b/.github/workflows/PR.yml @@ -1,4 +1,4 @@ -name: PR +name: PR Build on: pull_request: diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml new file mode 100644 index 000000000..a1c349215 --- /dev/null +++ b/.github/workflows/lint.yml @@ -0,0 +1,73 @@ +name: Lint + +on: + pull_request: + branches: [main] + paths: + - '**.md' + workflow_dispatch: + +jobs: + suggestions: + runs-on: ubuntu-latest + if: '! github.event.pull_request.head.repo.fork' + permissions: + pull-requests: write + steps: + - name: Checkout SAP repo + run: | + git config --global credential.helper "cache --timeout=3600" + echo -e "url=https://user:${GH_TOKEN}@github.com\n" | git credential approve + echo -e "url=https://user:${GH_TOKEN_PARENT}@github.tools.sap\n" | git credential approve + git clone --depth 1 --no-single-branch https://github.tools.sap/cap/docs docs + cd docs + git checkout $GITHUB_HEAD_REF || git checkout main + git submodule update --init --recursive + cd @external + git checkout $GITHUB_HEAD_REF + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + GH_TOKEN_PARENT: ${{ secrets.GH_TOKEN_PARENT }} + - name: Use Node.js + uses: actions/setup-node@v3 + with: + node-version: 18.x + cache: 'npm' + cache-dependency-path: docs/package-lock.json + - run: npm ci + working-directory: docs + - run: git checkout ${{ github.head_ref }} + working-directory: docs/@external + - name: Get changes + id: changes + working-directory: docs/@external + run: | + echo "DIFF_FILES=$(git diff --name-only --diff-filter=ACMRT ${{ github.event.pull_request.base.sha }} ${{ github.event.pull_request.head.sha }} -- '*.md' | xargs)" >> "$GITHUB_OUTPUT" + - name: Run cspell + id: cspell + continue-on-error: true + working-directory: docs/@external + run: | + npx cspell --no-progress --show-suggestions --show-context ${{ steps.changes.outputs.DIFF_FILES }} >> ${{ github.workspace }}/docs/@external/CSPELL.log + - name: Run markdownlint + id: markdownlint + continue-on-error: true + working-directory: docs/@external + run: | + npx markdownlint-cli --output ${{ github.workspace }}/docs/@external/MARKDOWNLINT.log ${{ steps.changes.outputs.DIFF_FILES }} + - name: Create review + id: create_review + if: steps.cspell.outcome == 'failure' || steps.markdownlint.outcome == 'failure' + uses: actions/github-script@v6 + env: + SHA: ${{ github.event.pull_request.head.sha }} + BASE_DIR: ${{ github.workspace }}/docs/@external + BASE_SHA: ${{ github.event.pull_request.base.sha }} + PULL_NUMBER: ${{ github.event.number }} + HEAD_SHA: ${{ github.event.pull_request.head.sha }} + REPO: 'docs' + REPO_OWNER: 'cap-js' + with: + script: | + const script = require('${{ github.workspace }}/docs/@external/.github/etc/create-review.cjs') + await script({github, context, core, require, exec}) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index fff6d41e1..958387472 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -37,8 +37,8 @@ jobs: - run: npm test - run: npm run docs:build env: - GH_BASE: / - SITE_HOSTNAME: https://verbose-bassoon-wl69yrk.pages.github.io + SITE_HOSTNAME: https://cap-js.github.io/docs + VITE_CAPIRE_PREVIEW: true - name: Upload artifact uses: actions/upload-pages-artifact@v1 with: diff --git a/.gitignore b/.gitignore index f56ec6580..8da5b6adb 100644 --- a/.gitignore +++ b/.gitignore @@ -3,3 +3,5 @@ node_modules/ cache/ dist/ .DS_Store +.idea +*.iml \ No newline at end of file diff --git a/.markdownlint.yaml b/.markdownlint.yaml new file mode 100644 index 000000000..ca4b4b4d3 --- /dev/null +++ b/.markdownlint.yaml @@ -0,0 +1,12 @@ +# For all rules see https://github.com/DavidAnson/markdownlint/blob/main/doc/Rules.md +default: false +fenced-code-language: true +no-reversed-links: true +code-fence-style: + style: backtick +# proper-names: +# names: +# - SQLite +# - VS Code +#no-bare-urls: true +no-empty-links: true diff --git a/.nvmrc b/.nvmrc new file mode 100644 index 000000000..3f430af82 --- /dev/null +++ b/.nvmrc @@ -0,0 +1 @@ +v18 diff --git a/.vitepress/config.ts b/.vitepress/config.ts index c7d5b3b80..7e78ca48e 100644 --- a/.vitepress/config.ts +++ b/.vitepress/config.ts @@ -1,8 +1,7 @@ import { UserConfig, DefaultTheme } from 'vitepress' -import { join } from 'node:path' +import { join, resolve } from 'node:path' import { promises as fs } from 'node:fs' import { sidebar as sideb, nav4 } from './menu' -import * as sitemap from './lib/sitemap' import * as redirects from './lib/redirects' import * as cdsMavenSite from './lib/cds-maven-site' @@ -14,33 +13,86 @@ export type CapireThemeConfig = DefaultTheme.Config & { } const siteHostName = process.env.SITE_HOSTNAME || 'http://localhost:4173' -const sitemapLinks: { url:string, lastmod?:number}[] = [] const redirectLinks: Record = {} const latestVersions = { - java_services: '2.0.1', - java_cds4j: '2.0.0' + java_services: '2.1.0', + java_cds4j: '2.1.0' } +const localSearchOptions = { + provider: 'local', + options: { + exclude: (relativePath) => relativePath.includes('/customization-old'), + miniSearch: { + options: { + tokenize: text => text.split( /[\n\r #%*,=/:;?[\]{}()&]+/u ), // simplified charset: removed [-_.@] and non-english chars (diacritics etc.) + processTerm: (term, fieldName) => { + term = term.trim().toLowerCase().replace(/^\.+/, '').replace(/\.+$/, '') + const stopWords = ['frontmatter', '$frontmatter.synopsis', 'and', 'about', 'but', 'now', 'the', 'with', 'you'] + if (term.length < 2 || stopWords.includes(term)) return false + + if (fieldName === 'text') { + // as we don't tokenize along . to keep expressions like `cds.requires.db`, split and add the single parts as extra terms + const parts = term.split('.') + if (parts.length > 1) { + const newTerms = [term, ...parts].filter(t => t.length >= 2).filter(t => !stopWords.includes(t)) + return newTerms + } + } + return term + }, + }, + searchOptions: { + combineWith: 'AND', + fuzzy: false, // produces too many bad results, like 'watch' finds 'patch' or 'batch' + // @ts-ignore + boostDocument: (documentId, term, storedFields:Record) => { + // downrate matches in archives, changelogs etc. + if (documentId.match(/\/archive|changelog|old-mtx-apis|java\/multitenancy/)) return -5 + + // downrate Java matches if Node is toggled and vice versa + const toggled = localStorage.getItem('impl-variant') + const titles = (storedFields?.titles as string[]).filter(t => !!t).map(t => t.toLowerCase()) + if (toggled === 'node' && (documentId.includes('/java/') || titles.includes('java'))) return -1 + if (toggled === 'java' && (documentId.includes('/node.js/') || titles.includes('node.js'))) return -1 + + // Uprate if term appears in titles. Add bonus for higher levels (i.e. lower index) + const titleIndex = titles.map((t, i) => t?.includes(term) ? i : -1).find(i => i>=0) ?? -1 + if (titleIndex >=0) return 10000 - titleIndex + + return 1 + } + } + } + } +} as { provider: 'local'; options?: DefaultTheme.LocalSearchOptions } + +const base = process.env.GH_BASE || '/docs/' const config:UserConfig = { title: 'CAPire', description: 'Documentation for SAP Cloud Application Programming Model', - base: process.env.GH_BASE || '/docs/', - srcExclude: ['**/README.md', '**/LICENSE.md', '**/CONTRIBUTING.md', '**/CODE_OF_CONDUCT.md', '**/menu.md'], + base, + srcExclude: ['**/README.md', '**/LICENSE.md', '**/CONTRIBUTING.md', '**/CODE_OF_CONDUCT.md', '**/menu.md', '**/PARKED-*.md'], themeConfig: { logo: '/assets/logos/cap.svg', get sidebar() { return sideb('menu.md') }, - get nav() { return [ - ...nav4(config.themeConfig!.sidebar).filter((i:any) => ['Getting Started', 'Cookbook'].includes(i.text)), - { text: 'Reference', items: [ - { text: 'CDS', link: 'cds/' }, - { text: 'Node.js', link: 'node.js/' }, - { text: 'Java', link: 'java/' }, - ] }, - ]}, - search: { - provider: 'local' + get nav() { + const navItems = nav4(config.themeConfig!.sidebar) as DefaultTheme.NavItem[] + return [ + navItems.find (i => i.text === 'Getting Started'), //@ts-ignore + ...navItems.filter(i => i.text === 'Cookbook').map((item:DefaultTheme.NavItemWithChildren) => { + item.items.unshift({ text: 'Overview', link: 'guides/' }) // add extra overview item to navbar + return item + }), + { text: 'Reference', items: [ + { text: 'CDS', link: 'cds/' }, + { text: 'Node.js', link: 'node.js/' }, + { text: 'Java', link: 'java/' }, + ]}, + ] as DefaultTheme.NavItem[] }, + search: localSearchOptions, footer: { message: 'Legal Disclosure | Terms of Use | Privacy', copyright: `Copyright © 2019-${new Date().getFullYear()} SAP SE` @@ -48,14 +100,17 @@ const config:UserConfig = { editLink: { pattern: 'https://github.com/cap-js/docs/edit/main/:path' }, + externalLinkIcon: true, socialLinks: [ - {icon: 'github', link: 'https://github.com/cap-js/'} + {icon: 'github', link: 'https://github.com/cap-js/docs'} ], outline: [1,3], capire: { versions: latestVersions, gotoLinks: [] } }, head: [ ['meta', { name: 'theme-color', content: '#db8b0b' }], + ['link', { rel: 'shortcut icon', href: base+'/assets/logos/favicon.ico' }], + ['link', { rel: 'apple-touch-icon', sizes: '180x180', href: base+'/assets/logos/apple-touch-icon.png' }] ], lastUpdated: true, cleanUrls: true, @@ -96,8 +151,12 @@ const config:UserConfig = { level: [2,3] } }, + sitemap: { + hostname: siteHostName + }, vite: { plugins: [ + //@ts-ignore redirects.devPlugin() ], build: { @@ -106,11 +165,11 @@ const config:UserConfig = { }, transformHtml(code, id, ctx) { redirects.collect(id, ctx.pageData.frontmatter, ctx.siteConfig, redirectLinks) - sitemap.collect(id, ctx, sitemapLinks) }, buildEnd: async ({ outDir, site }) => { await redirects.generate(outDir, site.base, redirectLinks) - await sitemap.generate(outDir, site.base, siteHostName, sitemapLinks) + await fs.writeFile(resolve(outDir, 'robots.txt'), `Sitemap: ${join(siteHostName, site.base, 'sitemap.xml')}\n`) + await cdsMavenSite.copySiteAssets(join(outDir, 'java/assets/cds-maven-plugin-site'), site) // zip assets aren't copied automatically, and `vite.assetInclude` doesn't work either @@ -121,4 +180,8 @@ const config:UserConfig = { } } +if (process.env.VITE_CAPIRE_PREVIEW) { + config.head!.push(['meta', { name: 'robots', content: 'noindex,nofollow' }]) +} + export default config diff --git a/.vitepress/lib/sitemap.ts b/.vitepress/lib/sitemap.ts deleted file mode 100644 index 431b2226f..000000000 --- a/.vitepress/lib/sitemap.ts +++ /dev/null @@ -1,27 +0,0 @@ -import { TransformContext } from 'vitepress' -import { resolve } from 'node:path' -import { createWriteStream } from 'node:fs' -import { promises as fs } from 'node:fs' -import { SitemapStream } from 'sitemap' - -const ignorePages = [/\.fragment\.html$/, /\/404\.html$/, /\/README\.html/, /\/CONTRIB.*\.html/, /\/CODE_OF.*\.html/, /\/links\.html/] - -export function collect(id:string, ctx:TransformContext, links: { url:string, lastmod?:number}[]) { - if (ignorePages.find(p => p.test(id))) return - const url = ctx.siteData.base + ctx.pageData.relativePath.replace(/index\.md$/, '').replace(/\.md$/, '') - links.push({ url, lastmod: ctx.pageData.lastUpdated }) -} - -export async function generate(outDir: string, base:string, hostname:string, links: { url:string, lastmod?:number}[]) { - console.debug('✓ writing sitemap.xml, robots.txt') - - const sitemap = new SitemapStream({ hostname }) - const writeStream = createWriteStream(resolve(outDir, 'sitemap.xml')) - sitemap.pipe(writeStream) - links.sort((l1, l2) => l1.url.localeCompare(l2.url)).forEach((link) => sitemap.write(link)) - sitemap.end() - await new Promise((r, e) => writeStream.on('finish', r).on('error', e)) - - const robots = `Sitemap: ${hostname}${base}sitemap.xml\n` - await fs.writeFile(resolve(outDir, 'robots.txt'), robots) -} diff --git a/.vitepress/theme/Layout.vue b/.vitepress/theme/Layout.vue index 9a0a05556..5196409ba 100644 --- a/.vitepress/theme/Layout.vue +++ b/.vitepress/theme/Layout.vue @@ -4,6 +4,9 @@ import ShortcutsList from './components/ShortcutsList.vue' import ImplVariants from './components/implvariants/ImplVariants.vue' import NavScreenMenuItem from './components/implvariants/NavScreenMenuItem.vue' import NotFound from './components/NotFound.vue' +import Ribbon from './components/Ribbon.vue' + +const isPreview = !!import.meta.env.VITE_CAPIRE_PREVIEW const { Layout } = DefaultTheme @@ -24,12 +27,20 @@ const { Layout } = DefaultTheme + + + DEV PREVIEW
+ See cap.cloud.sap +
+ diff --git a/.vitepress/theme/components/Ribbon.vue b/.vitepress/theme/components/Ribbon.vue new file mode 100644 index 000000000..e40dc4a1d --- /dev/null +++ b/.vitepress/theme/components/Ribbon.vue @@ -0,0 +1,141 @@ + + + + + \ No newline at end of file diff --git a/.vitepress/theme/components/indexFilter.js b/.vitepress/theme/components/indexFilter.js index 812fceb38..87cd988f3 100644 --- a/.vitepress/theme/components/indexFilter.js +++ b/.vitepress/theme/components/indexFilter.js @@ -1,5 +1,4 @@ -const { base, themeConfig: { sidebar }} = global.VITEPRESS_CONFIG.site -import { join } from 'node:path' +const { themeConfig: { sidebar }} = global.VITEPRESS_CONFIG.site export default (pages, basePath) => { let items = findInItems(basePath, sidebar) || [] @@ -20,7 +19,7 @@ export default (pages, basePath) => { .map(p => { // this data is inlined in each index page, so sparsely construct the final object return { - url : join(base, p.url), + url: p.url, title: p.title, frontmatter: { synopsis: p.frontmatter.synopsis diff --git a/.vitepress/theme/custom.scss b/.vitepress/theme/custom.scss index 3836cc297..0fd9cbc66 100644 --- a/.vitepress/theme/custom.scss +++ b/.vitepress/theme/custom.scss @@ -7,17 +7,13 @@ 'Helvetica Neue', Helvetica, Arial, 'Noto Sans', sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol', 'Noto Color Emoji'; --vp-c-brand: #db8b0b; + --vp-c-brand-dark: #9f660a; --vp-button-brand-border: #efbc6b; --vp-button-brand-hover-bg: #c37d0b; --vp-button-brand-hover-border: #f3b655; --vp-button-brand-active-bg: #9f660a; --vp-button-brand-active-border: #f3b655; - // --vp-c-brand: #039; - // --vp-c-brand-light: #003399dd; - // --vp-c-brand-dark: #0092d1; - - --vp-sidebar-width: 290px; // unset the intense --vp-c-green-lighter color to the default yellow marker color @@ -26,7 +22,7 @@ /* inverts image colors in dark mode */ &.dark { /* brightness fine-tuned to turn #fff into --vp-c-bg */ - img { filter: brightness(.884) invert(1) hue-rotate(177deg) } + img, svg.adapt-dark { filter: brightness(.884) invert(1) hue-rotate(177deg) } .VPHome img, .avatar img, img.ignore-dark { filter:none } img.mute-dark { filter:brightness(0.6) } } @@ -54,6 +50,10 @@ main { ul { padding-left: 2.2em; } + .caption { + text-align: center; + color: #888; + } div.language-zsh pre code { line-height: 1.4em } @@ -200,13 +200,6 @@ main { h3.method + h3.method { margin-top: 0 } - - a[target="_blank"]:not(.no-ext-link) { - &::after { - font-size: 0.7em; - content: "\00A0\2197"; - } - } } // Menu Sidebar @@ -309,4 +302,8 @@ tr:nth-child(odd) { [class*='language-'] pre { overflow: hidden !important; } + // expand navbar to cover wide content (see java/development/properties) + .content-body { + min-width: 1164px; + } } diff --git a/.vscode/settings.json b/.vscode/settings.json index 5b56e2dcb..2f38a8c48 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -5,6 +5,5 @@ "**/CONTRIBUTING.md": true, "**/LICENSE*": true, "**/node_modules/": true - }, - "github-enterprise.uri": "https://github.tools.sap" + } } \ No newline at end of file diff --git a/README.md b/README.md index 475c2db89..19fc3ee9c 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,6 @@ -# Documentation for SAP Cloud Application Programming Model +# Documentation for
SAP Cloud Application Programming Model + +[![REUSE status](https://api.reuse.software/badge/github.com/cap-js/docs)](https://api.reuse.software/info/github.com/cap-js/docs) ## About this project @@ -8,7 +10,7 @@ Along with additional SAP-specific pages the content is published to https://cap ## Support, Feedback, Contributing -This project is open to feature requests/suggestions, bug reports, etc. via [GitHub issues](https://github.com/SAP/cap-js/docs/issues) and [pull requests](https://github.com/SAP/cap-js/docs/pulls). Contribution and feedback are encouraged and always welcome. +This project is open to feature requests/suggestions, bug reports, etc. via [GitHub issues](https://github.com/cap-js/docs/issues) and [pull requests](https://github.com/cap-js/docs/pulls). Contribution and feedback are encouraged and always welcome. See our [contribution guidelines](CONTRIBUTING.md) for information about how to contribute, the project structure, as well as additional contribution information. diff --git a/about/capire.md b/about/capire.md index 469aceadd..3e16a1f85 100644 --- a/about/capire.md +++ b/about/capire.md @@ -9,7 +9,7 @@ status: released "Capire" (Italian for ‘understand’) is the name of our CAP documentation you're looking at right now. It's organized as follows: - [*About CAP*](../about/) — a brief introduction and overview of key concepts -- [*Getting Started*](#) — a few guides to get you started quickly +- [*Getting Started*](../get-started/) — a few guides to get you started quickly - [*Cookbook*](../guides/) — task-oriented guides from an app developer's point of view - [*Advanced*](../advanced/) — additional guides re peripheries and deep dives - [*Tools*](../tools/) — choose your preferred tools diff --git a/about/features.md b/about/features.md index 9e0f784dd..68b968aa1 100644 --- a/about/features.md +++ b/about/features.md @@ -53,7 +53,7 @@ Following is an index of the features currently covered by CAP, with status and | Logout from multitenant SaaS application | `cds logout` | | Subscribe a tenant to a SaaS application | `cds subscribe ` | | Unsubscribe a tenant from a SaaS application | `cds unsubscribe ` | -| Pull the base model for a SaaS extension | `cds pull ` | +| Pull the base model for a SaaS extension | `cds pull` | | Push a SaaS extension | `cds push` | @@ -87,9 +87,9 @@ Following is an index of the features currently covered by CAP, with status and | [— w/ Actions & Functions](../cds/cdl#actions) | | | [— w/ Events](../cds/cdl#events) | | | [Managed Compositions of Aspects](../cds/cdl#managed-compositions) | | -| Structured Elements | | -| Nested Projections | | -| Calculated Fields | | +| [Structured Elements](../cds/cdl#structured-types) | | +| Nested Projections | | +| [Calculated Elements](../cds/cdl#calculated-elements) | | | Managed _n:m_ Associations | | | Pluggable CDS linter | | | [CDS linter](../tools/#cds-lint) | | @@ -192,16 +192,15 @@ Following is an index of the features currently covered by CAP, with status and |-------------------------------------------------|:----------:|:------------------:|:----:| | [SAP HANA](../guides/databases) | | | | | [SAP HANA Cloud](../guides/databases-hana) | | | | -| [PostgreSQL](../guides/databases-postgres) | | 1 | | -| [SQLite](../guides/databases-sqlite) 2 | | | | -| [H2](../java/persistence-services#h2) 2 | | | | +| [PostgreSQL](../guides/databases-postgres) | | | | +| [SQLite](../guides/databases-sqlite) 1 | | | | +| [H2](../java/persistence-services#h2) 1 | | | | | [MongoDB](../guides/databases) out of the box | | | | | Pluggable drivers architecture | | | | | Out-of-the-box support for other databases? | | | | -> 1 For Node.js, see community project [cds-pg - PostgreSQL adapter for SAP CDS](https://www.npmjs.com/package/cds-pg).
-> 2 To speed up development. Not for productive use!
+> 1 To speed up development. Not for productive use!
> You can already integrate your database of choice in a project or a contribution level. The last two are meant to further facilitate this by out-of-the-box features in CAP. diff --git a/about/glossary.md b/about/glossary.md index bcf393e53..1bbcf97d9 100644 --- a/about/glossary.md +++ b/about/glossary.md @@ -12,13 +12,13 @@ CAP-related Terms and Acronyms {.subtitle}
-[CAP]() — shorthand for "SAP Cloud Application Programming Model" +[CAP](./glossary.md) — shorthand for "SAP Cloud Application Programming Model" : Not an official name, though. -[capire]() — Italian for 'understand' +[capire](./glossary.md) — Italian for 'understand' : ... and the title of these pages and documentation of CAP. -[tl;dr]() — too long; didn't read +[tl;dr](./glossary.md) — too long; didn't read : a common social phenomenon these days. Therefore, capire is rather meagre with text and greased with code. You have to read between the lines, sorry. --- diff --git a/about/index.md b/about/index.md index 02c2873ba..768120939 100644 --- a/about/index.md +++ b/about/index.md @@ -104,13 +104,10 @@ CAP places **primary focus on domain**, by capturing _domain knowledge_ and _int The figure below illustrates the prevalent use of CDS models (in the left column), which fuel generic runtimes, like the CAP service runtimes or databases. -
- -
Anatomy of a Typical Application
-
-
+![CDS models are used to define domain models, service models and UI markup. You can add custom code on top of them.](../assets/core-concepts.drawio.svg) +Anatomy of a Typical Application{.caption} -

+
###### Core Data Services (CDS) @@ -474,5 +471,5 @@ Finally, projects are encouraged to **parallelize workloads**. For example, foll ---
-1 *GraphQL* and *Kafka* aren't supported out of the box today, but might be added in future. +1 *Kafka* isn't supported out of the box today, but might be added in future.
diff --git a/advanced/analytics.md b/advanced/analytics.md index 82002e435..af2d455c3 100644 --- a/advanced/analytics.md +++ b/advanced/analytics.md @@ -9,7 +9,7 @@ redirect_from: guides/analytics # Analytics in OData V2 -
+{{ $frontmatter.synopsis }} ## Aggregation diff --git a/advanced/fiori.md b/advanced/fiori.md index 559327933..0a25ec9a5 100644 --- a/advanced/fiori.md +++ b/advanced/fiori.md @@ -11,7 +11,7 @@ uacp: Used as link target from Help Portal at https://help.sap.com/products/BTP/ # Serving Fiori UIs -
+{{ $frontmatter.synopsis }} This guide explains how to add one or more SAP Fiori elements apps to a CAP project, how to add SAP Fiori elements annotations to respective service definitions, and more. In the following sections, when mentioning Fiori, we always mean SAP Fiori elements. @@ -416,6 +416,11 @@ annotate sap.capire.bookshop.Books with @fiori.draft.enabled; > Background: SAP Fiori drafts require single keys of type `UUID`, which isn’t the case by default for the automatically generated `_texts` entities (→ [see the _Localized Data_ guide for details](../guides/localized-data#behind-the-scenes)). The `@fiori.draft.enabled` annotation tells the compiler to add such a technical primary key element named `ID_texts`. +::: warning +Adding the annotation `@fiori.draft.enabled` only works as long as the corresponding `_texts` entities +don't yet contain any entries, because existing entries don't have a value for the new key field `ID_texts`. +::: + ![An SAP Fiori UI showing how a book is edited in the bookshop sample and that the translations tab is used for non-standard languages.](../assets/draft-for-localized-data.png){style="margin:0"} [See it live in **cap/samples**.](https://github.com/sap-samples/cloud-cap-samples/tree/main/fiori/app/admin-books/fiori-service.cds#L50){.learn-more} @@ -433,28 +438,8 @@ You can add your validation logic before operation event handlers. Specific even ###### ... in Node.js -You can add your validation logic before the operation handler for the `CREATE` or `UPDATE` event (as in the case of nondraft implementations) or on the `SAVE` event (specific to drafts only): - -```js -srv.before ('CREATE','Books', (req)=>{ ... }) // run before create -srv.before ('UPDATE','Books', (req)=>{ ... }) // run before create -srv.before ('SAVE','Books', (req)=>{...}) // run at final save only -``` - -In addition, you can add field-level validations on the individual `PATCH` events: +You can add your validation logic before the operation handler for either CRUD or draft-specific events. See [Node.js > Fiori Support > Handlers Registration](../node.js/fiori#draft-support) for more details about handler registration. -```js -srv.before ('PATCH','Books', (req)=>{...}) // run during editing -``` - -> These get triggered during the draft edit session whenever the user tabs from one field to the next, and can be used to provide early feedback. - -You can also add custom logic for the initial creation of drafts: - -```js -srv.before ('NEW','Books', (req)=>{...}) // run during creation of a draft from scratch -srv.before ('EDIT','Books', (req)=>{...}) // run during creation of a draft for existing instance -```
diff --git a/advanced/hana.md b/advanced/hana.md index 5c0d10184..198c8392c 100644 --- a/advanced/hana.md +++ b/advanced/hana.md @@ -8,7 +8,7 @@ status: released # Using Native SAP HANA Artifacts -
+{{ $frontmatter.synopsis }} ## Introduction diff --git a/advanced/hybrid-testing.md b/advanced/hybrid-testing.md index cb155397f..8398425b5 100644 --- a/advanced/hybrid-testing.md +++ b/advanced/hybrid-testing.md @@ -33,7 +33,7 @@ cds bind also supports Cloud Foundry _user-provided_ services. Output: -``` +```log [bind] - Retrieving data from Cloud Foundry... [bind] - Binding db to Cloud Foundry managed service my-hana:my-hana-key with kind hana. [bind] - Saving bindings to .cdsrc-private.json in profile hybrid. @@ -94,7 +94,7 @@ Binds your local CAP application to the user provided service instance `my-user- Output: -``` +```log [bind] - Retrieving data from Cloud Foundry... [bind] - Binding my-ups to Cloud Foundry user provided service my-user-provided-service. [bind] - Saving bindings to .cdsrc-private.json in profile hybrid. @@ -119,7 +119,7 @@ The command uses your current Kubernetes context. That is your current server an To list all **Service Bindings** in your current Kubernetes context, you can use the `kubectl get servicebindings` command: -``` +```log NAME SERVICE-INSTANCE SECRET-NAME STATUS AGE cpapp-xsuaa-binding cpapp-xsuaa cpapp-xsuaa-secret Ready 11s ``` @@ -132,7 +132,7 @@ cds bind -2 cpapp-xsuaa-binding --on k8s Output: -``` +```log [bind] - Retrieving data from Kubernetes... [bind] - Binding uaa to Kubernetes service binding cpapp-xsuaa-binding with kind xsuaa [bind] - Saving bindings to .cdsrc-private.json in profile hybrid @@ -173,7 +173,7 @@ Alternatively, you can bind to Kubernetes **Secrets**. You can use the `kubectl get secrets` command to list all secrets in your current Kubernetes context: -``` +```log NAME TYPE DATA AGE cap-hdi-container Opaque 11 44h ``` @@ -188,7 +188,7 @@ cds bind -2 cap-hdi-container --on k8s --kind hana Output: -``` +```log [bind] - Retrieving data from Kubernetes... [bind] - Binding db to Kubernetes secret cap-hdi-container with kind hana [bind] - Saving bindings to .cdsrc-private.json in profile hybrid @@ -239,10 +239,6 @@ Example output: } ``` -::: warning -Only `cds watch` and `cds env` (the latter with the `--resolve-bindings` option) resolve cloud bindings. Bindings are resolved by `cds serve` or `cds exec`. -::: - ### Run Arbitrary Commands with Service Bindings With `cds bind` you avoid storing credentials on your hard disk. If you need to start other applications with cloud service bindings from local, then you can use the [`exec` sub command](#cds-bind-exec) of `cds bind`. diff --git a/advanced/monitoring.md b/advanced/monitoring.md index a22133d66..c0ede4143 100644 --- a/advanced/monitoring.md +++ b/advanced/monitoring.md @@ -6,7 +6,7 @@ status: released # Monitoring -
+{{ $frontmatter.synopsis }} ## Dynatrace diff --git a/advanced/odata.md b/advanced/odata.md index 946f537b3..a8014a8eb 100644 --- a/advanced/odata.md +++ b/advanced/odata.md @@ -26,33 +26,98 @@ status: released ## Feature Overview { #overview} -OData is an OASIS standard, which essentially enhances plain REST with standardized query options like `$select`, `$expand`, `$filter`, etc. Find a rough overview of the feature coverage in the following table. +OData is an OASIS standard, which essentially enhances plain REST with standardized system query options like `$select`, `$expand`, `$filter`, etc. Find a rough overview of the feature coverage in the following table: | Query Options | Remarks | Node.js | Java | |----------------|-------------------------------------------|------------|---------| -| `$search` | Search in multiple/all text elements(3) | | | +| `$search` | Search in multiple/all text elements(1)| | | | `$value` | Retrieves single rows/values | | | | `$top`,`$skip` | Requests paginated results | | | | `$filter` | Like SQL where clause | | | | `$select` | Like SQL select clause | | | | `$orderby` | Like SQL order by clause | | | | `$count` | Gets number of rows for paged results | | | -| [Delta Payload](https://docs.oasis-open.org/odata/odata/v4.01/odata-v4.01-part1-protocol.html#sec_DeltaPayloads) | For nested entity collections in deep update | | | | `$apply` | For [data aggregation](#data-aggregation) | | | -| `$expand` | Deep-read associated entities | (1) | (2) | -| [Lambda Operators](https://docs.oasis-open.org/odata/odata/v4.01/odata-v4.01-part2-url-conventions.html#_Toc31361024) | Boolean expressions on a collection | | (4) | +| `$expand` | Deep-read associated entities | | | +| [Lambda Operators](https://docs.oasis-open.org/odata/odata/v4.01/odata-v4.01-part2-url-conventions.html#_Toc31361024) | Boolean expressions on a collection | | (2) | - +- (1) The elements to be searched are specified with the [`@cds.search` annotation](../guides/providing-services#searching-data). +- (2) The navigation path identifying the collection can only contain one segment. -- (1) Support for nested `$select`, `$expand`, `$filter` and `$orderby` options. -- (2) Support for nested `$select`, `$expand`, `$filter`, `$orderby`, `$top` and `$skip` options. -- (3) The elements to be searched are specified with the [`@cds.search` annotation](../guides/providing-services#searching-data). -- (4) Current limitation: Navigation path identifying the collection can only contain one segment. +System query options can also be applied to an [expanded navigation property](http://docs.oasis-open.org/odata/odata/v4.01/odata-v4.01-part2-url-conventions.html#_Toc31361039) (nested within `$expand`): + +| Query Options | Remarks | Node.js | Java | +|----------------|-------------------------------------------|----------|--------| +| `$select` | Select properties of associated entities | | | +| `$filter` | Filter associated entities | | | +| `$expand` | Nested expand | | | +| `$orderby` | Sort associated entities | | | +| `$top`,`$skip` | Paginate associated entities | | | +| `$count` | Count associated entities | | | +| `$search` | Search associated entities | | | [Learn more in the **Getting Started guide on odata.org**.](https://www.odata.org/getting-started/){.learn-more} [Learn more in the tutorials **Take a Deep Dive into OData**.](https://developers.sap.com/mission.scp-3-odata.html){.learn-more} +| Data Modification | Remarks | Node.js | Java | +|-------------------|-------------------------------------------|------------|---------| +| [Create an Entity](https://docs.oasis-open.org/odata/odata/v4.01/odata-v4.01-part1-protocol.html#sec_CreateanEntity) | `POST` request on Entity collection | | | +| [Update an Entity](https://docs.oasis-open.org/odata/odata/v4.01/odata-v4.01-part1-protocol.html#sec_UpdateanEntity) | `PATCH` or `PUT` request on Entity | | | +[ETags](https://docs.oasis-open.org/odata/odata/v4.01/odata-v4.01-part1-protocol.html#sec_UseofETagsforAvoidingUpdateConflicts) | For avoiding update conflicts | | | +| [Delete an Entity](https://docs.oasis-open.org/odata/odata/v4.01/odata-v4.01-part1-protocol.html#sec_DeleteanEntity) | `DELETE` request on Entity | | | +| [Delta Payloads](https://docs.oasis-open.org/odata/odata/v4.01/odata-v4.01-part1-protocol.html#sec_DeltaPayloads) | For nested entity collections in [deep updates](https://docs.oasis-open.org/odata/odata/v4.01/odata-v4.01-part1-protocol.html#sec_UpdateRelatedEntitiesWhenUpdatinganE) | | | +| [Patch Collection](#odata-patch-collection) | Update Entity collection with [delta](https://docs.oasis-open.org/odata/odata/v4.01/odata-v4.01-part1-protocol.html#sec_DeltaPayloads) | | (beta) | + + +## PATCH Entity Collection with Mass Data { #odata-patch-collection } + +With OData v4, you can [update a collection of entities](https://docs.oasis-open.org/odata/odata/v4.01/odata-v4.01-part1-protocol.html#sec_UpdateaCollectionofEntities) with a _single_ PATCH request. +The resource path of the request targets the entity collection and the body of the request is given as a [delta payload](https://docs.oasis-open.org/odata/odata/v4.01/odata-v4.01-part1-protocol.html#sec_DeltaPayloads): + +```js +PATCH /CatalogService/Books +Content-Type: application/json + +{ + "@context": "#$delta", + "value": [ + { + "ID": 17, + "title": "CAP - what's new in 2023", + "price": 29.99, + "author_ID": 999 + }, + { + "ID": 85, + "price": 9.99 + }, + { + "ID": 42, + "@removed": { "reason": "deleted" } + } + ] +} +``` + +PATCH requests with delta payload are executed using batch delete and [upsert](../java/query-api#bulk-upsert) statements, and are more efficient than OData [batch requests](http://docs.oasis-open.org/odata/odata/v4.01/csprd02/part1-protocol/odata-v4.01-csprd02-part1-protocol.html#sec_BatchRequests). + +Use PATCH on entity collections for uploading mass data using a dedicated service, which is secured using [role-based authorization](../java/security#role-based-auth). Delta updates must be explicitly enabled by annotating the entity with + +```cds +@Capabilities.UpdateRestrictions.DeltaUpdateSupported +``` + +Limitations: + * Conflict detection via [ETags](../guides/providing-services#etag) is not supported. + * [Draft flow](../java/fiori-drafts#bypassing-draft-flow) is bypassed, `IsActiveEntity` has to be `true`. + * [Draft locks](../java/fiori-drafts#draft-lock) are ignored, active entities are updated or deleted w/o canceling drafts. + * [Added and deleted links](https://docs.oasis-open.org/odata/odata/v4.01/odata-v4.01-part1-protocol.html#sec_IteminaDeltaPayloadResponse) are not supported. + * The header `Prefer=representation` is not yet supported. + * The `continue-on-error` preference is not yet supported. + * The generic CAP handler support for [upsert](../java/query-api#upsert) is limited, for example, audit logging is not supported. + + ## Mapping of CDS Types { #type-mapping} The table below lists [CDS's built-in types](../cds/types) and their mapping to the OData EDM type system. @@ -107,7 +172,7 @@ entity Foo { Another prominent use case is the CDS type `UUID`, which maps to `Edm.Guid` by default. However, the OData standard puts up restrictive rules for _Edm.Guid_ values - for example, only hyphenated strings are allowed - which can conflict with existing data. -Therefore, you can overridde the default mapping as follows: +Therefore, you can override the default mapping as follows: ```cds entity Books { @@ -664,7 +729,7 @@ With this configuration, all annotations prefixed with `MyVocabulary` are consid ```cds service S { - @MyVocabulary.MyAnno: 'test' + @MyVocabulary.MyAnno: 'My new Annotation' entity E { /*...*/ }; }; ``` @@ -864,15 +929,15 @@ The cds build for OData v4 will render the entity type `Book` in `edmx` with the ``` -The entity `Book` is open, allowing the client to enrich the entity with additional properties, e.g.: +The entity `Book` is open, allowing the client to enrich the entity with additional properties, e.g.: ```json {"id": 1, "title": "Tow Sawyer"} -``` +``` or ```json -{"title": "Tow Sawyer", +{"title": "Tow Sawyer", "author": { "name": "Mark Twain", "age": 74 } } ``` @@ -928,7 +993,6 @@ The full support of Open Types (`@open`) in OData is currently available for the The Node.js runtime supports the feature only in REST Adapter as well as for parameters and return types of actions and functions. ::: -
## Singletons @@ -1003,7 +1067,7 @@ For Node.js projects, add the proxy as express.js middleware as follows: 2. Add this as a plugin to your project: - ::: code-group + ::: code-group ```json [package.json] {... "cds" { @@ -1013,7 +1077,7 @@ For Node.js projects, add the proxy as express.js middleware as follows: } } ``` - + ```json [.cdsrc.json] { "cov2ap" : { diff --git a/advanced/performance-modeling.md b/advanced/performance-modeling.md index 151403d7b..d01b08d83 100644 --- a/advanced/performance-modeling.md +++ b/advanced/performance-modeling.md @@ -9,7 +9,7 @@ status: released # Performance Modeling -
+{{ $frontmatter.synopsis }} ## Avoid UNION diff --git a/advanced/troubleshooting.md b/advanced/troubleshooting.md index a72bc2ea0..3348e84b4 100644 --- a/advanced/troubleshooting.md +++ b/advanced/troubleshooting.md @@ -9,7 +9,7 @@ uacp: This page is linked from the Help Portal at https://help.sap.com/products/ # Troubleshooting -
+{{ $frontmatter.synopsis }} ## General { #cds} @@ -107,7 +107,7 @@ restart Ports can be explicitly set with the `PORT` environment variable or the `--port` argument. See `cds help run` for more. -### Why do I loose registered event handlers? +### Why do I lose registered event handlers? Node.js allows extending existing services, for example in mashup scenarios. This is commonly done on bootstrap time in `cds.on('served', ...)` handlers like so: @@ -175,7 +175,7 @@ module.exports = cds.server | | Explanation | | --- | ---- | -| _Root Cause_ | Most probably, this error is caused by the destination timeout of the AppRouter. +| _Root Cause_ | Most probably, this error is caused by the destination timeout of the App Router. | _Solution_ | Set your own `timeout` configuration of [@sap/approuter](https://www.npmjs.com/package/@sap/approuter#destinations). ### Why does the server crash with `No service definition found for `? @@ -376,7 +376,7 @@ You can disable the database clustering for the update. | _Solution_ | To install it, follow these [instructions](https://help.sap.com/docs/SAP_DATA_SERVICES/e54136ab6a4a43e6a370265bf0a2d744/c049e28431ee4e8280cd6f5d1a8937d8.html). If this doesn't solve the problem, also set the environment variables as [described here](https://help.sap.com/docs/SAP_HANA_PLATFORM/e7e79e15f5284474b965872bf0fa3d63/463d3ceeb7404eca8762dfe74e9cff62.html). -#### Deployment fails --- +#### Deployment fails — + _Failed to get connection for database_ + _Connection failed (RTE:[300015] SSL certificate validation failed_ + _Cannot create SSL engine: Received invalid SSL Record Header_ @@ -386,6 +386,13 @@ You can disable the database clustering for the update. | _Root Cause_ | Your SAP HANA Cloud instance is stopped. | | _Solution_ | [Start your SAP HANA Cloud instance.](https://help.sap.com/docs/HANA_CLOUD/9ae9104a46f74a6583ce5182e7fb20cb/fe8cbc3a13b4425990880bac3a5d50d9.html) +#### Deployment fails — SSL certificate validation failed: error code: 337047686 + +| | Explanation | +| --- | ---- | +| _Root Cause_ | The `@sap/hana-client` can't verify the certificate because of missing system toolchain dependencies. | +| _Solution_ | Make sure [`ca-certificates`](https://packages.ubuntu.com/focal/ca-certificates) is installed on your Docker container. + #### Deployment fails — _Cannot create SSL engine: Received invalid SSL Record Header_ | | Explanation | @@ -624,69 +631,28 @@ Mixing them together is not trivial, therefore only some special cases are suppo ## MTX (legacy) -This refers to potential problems with [@sap/cds-mtx](../guides/multitenancy/old-mtx-apis). +This refers to potential problems with the **deprecated** [@sap/cds-mtx](../guides/multitenancy/old-mtx-apis) package. -### How Do I Setup a Sidecar with AppRouter? { #mtx-as-sidecar-with-approuter} +### How do I set up MTX with App Router? { #mtx-as-sidecar-with-approuter} See [Deploy to Cloud Foundry](../guides/deployment/to-cf) for the basic project and deployment setup. -### I get a 401 error, when logging in to MTX-Sidecar through AppRouter { #mtx-sidecar-approuter-401} - -1. Enable token forwarding in AppRouter, for example using _mta.yaml_: - - ```yaml - - name: approuter - requires: - - name: mtx-sidecar - group: destinations - properties: - name: mtx-sidecar - url: ~{url} - forwardAuthToken: true - ``` - -2. Configure a route to MTX-Sidecar without authentication, so that the MTX-Sidecar can handle authentication and authorization. - - In _xs-app.json_, add the following entry: - - ```json - "routes": [ - { - "source": "^/extend/(.*)", - "destination": "mtx-sidecar", - "target": "$1", - "authenticationType": "none" - } - ] - ``` - - Make the following adjustments: - - `source` reflects the URL path to be used for the login and extending the SaaS app. - Based on the previous example, it would be: - - ```sh - cds login … /extend - ``` - - - `destination` is the name of the destination pointing to MTX as defined in [mta.yaml](https://help.sap.com/docs/CP_CONNECTIVITY/cca91383641e40ffbe03bdc78f00f681/8aeea65eb9d64267b554f64a3db8a349.html) - or [manifest.yml](https://help.sap.com/docs/BTP/65de2977205c403bbc107264b8eccf4b/3cc788ebc00e40a091505c6b3fa485e7.html#destinations), e.g.: - - ```yaml - modules: - - name: sidecar - provides: - - name: mtx-sidecar - properties: - url: ${default-url} - ``` - -3. When logging in, specify the same subdomain you used to get a passcode. Normally this will be the subdomain of the customer subaccount: - - ```sh - cds login … -s - ``` - -Alternatively, use the options mentioned with `cds extend` without login. +### I get a 401 error when logging in to MTX through App Router { #mtx-sidecar-approuter-401} + +See [App Router configuration](../guides/multitenancy/old-mtx-apis#approuter-config) to ensure a correct handling of authentication by both `@sap/approuter` and `@sap/cds-mtx`. + +When logging in, remember to specify the same subdomain you used to get a passcode. Normally this will be the subdomain of the customer subaccount: + +```sh +cds login … -s +``` + +Alternatively, without login: + +```sh +cds extend … -s +``` + ## CAP on Kyma diff --git a/cds/annotations.md b/cds/annotations.md index b4cc422c7..053b74d6a 100644 --- a/cds/annotations.md +++ b/cds/annotations.md @@ -1,5 +1,5 @@ --- -layout: cds-ref +# layout: cds-ref shorty: Annotations synopsis: > Find here a reference and glossary of common annotations intrinsically supported by the CDS compiler and runtimes. @@ -9,7 +9,7 @@ uacp: Used as link target from Help Portal at https://help.sap.com/products/BTP/ # Common Annotations -
+{{ $frontmatter.synopsis }} [Learn more about the syntax of annotations.](./cdl#annotations){.learn-more} diff --git a/cds/cdl.md b/cds/cdl.md index 6b477ad91..a40530d75 100644 --- a/cds/cdl.md +++ b/cds/cdl.md @@ -148,7 +148,8 @@ type EmailAddress : { kind:String; address:String; } When deployed to SQL databases, such fields are mapped to [LargeString](types) columns and the data is stored denormalized as JSON array. With OData V4, arrayed types are rendered as `Collection` in the EDM(X). -::: danger + +::: warning Filter expressions, [instance-based authorization](../guides/authorization#instance-based-auth) and [search](../guides/providing-services#searching-data) are not supported on arrayed elements. ::: @@ -156,18 +157,19 @@ Filter expressions, [instance-based authorization](../guides/authorization#insta For arrayed types the `null` and `not null` constraints apply to the _members_ of the collections. The default is `not null` indicating that the collections can't hold `null` values. -::: danger +::: warning An empty collection is represented by an empty JSON array. A `null` value is invalid for an element with arrayed type. ::: -In the following example the collection `emails` may hold members that are `null`. It may also hold a member where the element `kind` is `null`. The collection `email` must not be `null`! +In the following example the collection `emails` may hold members that are `null`. It may also hold a member where the element `kind` is `null`. +The collection `emails` itself must not be `null`! ```cds entity Bar { emails : many { kind : String null; address : String not null; - } null; + } null; // -> collection emails may hold null values, overwriting default } ``` @@ -313,7 +315,6 @@ in queries. Some restrictions apply: * Subqueries are not allowed. * Nested projections (inline/expand) are not allowed. -* Referencing localized elements is not allowed. * A calculated element can't be key. A calculated element can be *used* in every location where an expression can occur. A calculated element can't be used in the following cases: @@ -356,7 +357,7 @@ CREATE TABLE Employees ( name NVARCHAR GENERATED ALWAYS AS (firstName || ' ' || lastName) ); ``` -For the definition of calculated elements on-write, the same restrictions apply as for the on-read variant. +For the definition of calculated elements on-write, all the on-read variant's restrictions apply and referencing localized elements isn't allowed. In addition, there are restrictions that depend on the particular database. Currently all databases supported by CAP have a common restriction: The calculation expression may only refer to fields of the same table row. Therefore, such an expression must not contain subqueries, aggregate functions, or paths with associations. @@ -1117,14 +1118,14 @@ They're based on a mixin approach as known from Aspect-oriented Programming meth Use `extend` to add extension fields or to add/override metadata to existing definitions, for example, annotations, as follows: ```cds -extend Foo with @title:'Foo' { +extend Foo with @(title: 'Foo') { newField : String; extend nestedStructField { newField : String; extend existingField @title:'Nested Field'; } } -extend Bar with @title:'Bar'; // nothing for elements +extend Bar with @title: 'Bar'; // nothing for elements ``` ::: tip @@ -1145,6 +1146,9 @@ extend Books:price.value with (precision:12,scale:3); ``` The extended type or element directly must have the respective property. +For multiple conflicting `extend` statements, the last `extend` wins, i.e. in three files `a.cds <- b.cds <- c.cds`, where `<-` means `using from`, +the `extend` from `c.cds` is applied, as it is the last in the dependency chain. + ### Named Aspects — `define aspect` {#aspect} diff --git a/cds/common.md b/cds/common.md index 887f05e5d..f6a2185dc 100644 --- a/cds/common.md +++ b/cds/common.md @@ -1,5 +1,5 @@ --- -layout: cds-ref +# layout: cds-ref synopsis: > Introduces @sap/cds/common, a prebuilt CDS model shipped with @sap/cds that provides common types and aspects. status: released diff --git a/cds/compiler-v2.md b/cds/compiler-v2.md index 3ae3ecf4d..52d1fbad0 100644 --- a/cds/compiler-v2.md +++ b/cds/compiler-v2.md @@ -4,14 +4,14 @@ synopsis: > CDS compiler version 2 (cv2) brings numerous improvements, which allow us to significantly streamline model processing going forward. All projects are recommended to upgrade as soon as possible, as the former version will only receive critical fixes after cv2 is released. -layout: cds-ref +# layout: cds-ref redirect_from: releases/compiler-v2 status: released --- # Upgrade to Compiler v2 -
+{{ $frontmatter.synopsis }} Changes mostly affect internal implementations of the compiler and nonpublic parts of the artifacts produced by the compiler (CSN, EDMX, ...), hence are unlikely to be observed by users of CDS. @@ -799,7 +799,7 @@ Your custom coded needs to be adapted accordingly. This will not be necessary if ### Interfaces for Inline Defined Types -CDS allows to use anonymous inline defined types in a service, for example as items types of an arryed type or as a return type of a function: +CDS allows to use anonymous inline defined types in a service, for example as items types of an arrayed type or as a return type of a function: ```cds service hr { diff --git a/cds/cql.md b/cds/cql.md index 73561b826..a44035a91 100644 --- a/cds/cql.md +++ b/cds/cql.md @@ -1,5 +1,5 @@ --- -layout: cds-ref +# layout: cds-ref shorty: Query Language synopsis: > Documents the CDS Query Language (aka CQL) which is an extension of the standard SQL SELECT statement. diff --git a/cds/cqn.md b/cds/cqn.md index 5f8c99a88..e0faa1a80 100644 --- a/cds/cqn.md +++ b/cds/cqn.md @@ -1,5 +1,5 @@ --- -layout: cds-ref +# layout: cds-ref shorty: Query Notation synopsis: > Specification of the Core Query Notation (CQN) format that is used to capture queries as plain JavaScript objects. diff --git a/cds/csn.md b/cds/csn.md index 5788e9290..af24651d0 100644 --- a/cds/csn.md +++ b/cds/csn.md @@ -1,5 +1,5 @@ --- -layout: cds-ref +# layout: cds-ref shorty: Schema Notation synopsis: > Specification of CSN, CDS' canonical format for representing CDS models as plain JavaScript objects, similar to JSON Schema. diff --git a/cds/cxn.md b/cds/cxn.md index a9398e8a2..eae944dfc 100644 --- a/cds/cxn.md +++ b/cds/cxn.md @@ -1,5 +1,5 @@ --- -layout: cds-ref +# layout: cds-ref shorty: Expressions synopsis: > Specification of the Core Expression Notation (CXN) used to capture expressions as plain JavaScript objects. diff --git a/cds/models.md b/cds/models.md index 0e1e42ebc..02adcc81f 100644 --- a/cds/models.md +++ b/cds/models.md @@ -1,5 +1,5 @@ --- -layout: cds-ref +# layout: cds-ref shorty: Nature of Models synopsis: > Introduces the fundamental principles of CDS models. @@ -9,7 +9,7 @@ status: released # The Nature of Models -
+{{ $frontmatter.synopsis }} Models in `cds` are plain JavaScript objects conforming to the _[Core Schema Notation (CSN)](./csn)_. They can be parsed from [_.cds_ sources](./cdl), read from _.json_ or _.yaml_ files or dynamically created in code at runtime. diff --git a/cds/types.md b/cds/types.md index b2a1871f4..671a1eb06 100644 --- a/cds/types.md +++ b/cds/types.md @@ -1,5 +1,5 @@ --- -layout: cds-ref +# layout: cds-ref shorty: Built-in Types synopsis: > Find here a brief overview of the predefined types shipped with CDS. diff --git a/cspell.config.yaml b/cspell.config.yaml new file mode 100644 index 000000000..c13fc0e07 --- /dev/null +++ b/cspell.config.yaml @@ -0,0 +1,89 @@ +--- +$schema: https://raw.githubusercontent.com/streetsidesoftware/cspell/main/cspell.schema.json +version: '0.2' +language: en-US,en-GB +dictionaryDefinitions: + - name: project-words-internal + path: '../project-words.txt' + addWords: true + - name: project-words-external + path: './project-words.txt' + addWords: true +dictionaries: + - project-words-internal + - project-words-external + - companies + - softwareTerms + - misc + - node + - typescript + - bash + - html + - css +ignorePaths: + - 'node_modules' + - 'project-words.txt' + - '.github' +allowCompoundWords: true +caseSensitive: false +ignoreRegExpList: + - code_block + - inline_code_block + - code-group + - href_link + - markdown_link + - fragments + - html_comment + - heading + - heading_ids + - bold_text + - italic_text + - i_tags + - cli_option + - property + - property_in_code_block +patterns: + - name: code_block + pattern: /`{3}[\s\S]*?`{3}/gm + + - name: inline_code_block + pattern: /\`[-.]?[\s\S]*?(?!\w)\`/gm + + - name: html_comment + pattern: //gm + + - name: markdown_link + pattern: /-?\s*([*#]|\[.*?\])\(.*?\)/gm + + - name: heading_ids + pattern: /\{\s*#+.+\}/ + + - name: code-group + pattern: /:{3}\scode-group[\s\S]*?:{3}/gm + + - name: href_link + pattern: /href="[\s\S]*"/gm + + - name: fragments + pattern: /<(Fragment|span|div)\s+(id|target)="#?.*"[\s.]*/?>/g + + - name: heading + pattern: /^(#{1,6}.*)/ + + - name: bold_text + pattern: /\*{2}.*\*{2}|__.*__/gm + + - name: italic_text + pattern: /\*.*\*|_.*_/gm + + - name: i_tags + pattern: /.*<\/i>/g + + - name: cli_option + pattern: /(-[^\s]+)/g + + - name: property + pattern: /`?`\.[a-zA-Z]*?/g + + - name: property_in_code_block + pattern: /`(\..*)+?`/gm diff --git a/get-started/hello-world.md b/get-started/hello-world.md index 5ef444fd0..ee5494ba4 100644 --- a/get-started/hello-world.md +++ b/get-started/hello-world.md @@ -28,8 +28,9 @@ Let's create a simple _Hello World_ OData service using the SAP Cloud Applicatio
```sh -cds init hello-world --add samples +cds init hello-world --add tiny-sample cd hello-world +npm install ```
@@ -92,8 +93,6 @@ module.exports = class say { ... for example, using a [CAP Java](../java/provisioning-api) custom handler like this: -
- ::: code-group ```java [srv/src/main/java/customer/hello_world/handlers/HelloHandler.java] @@ -122,6 +121,8 @@ public class HelloHandler implements EventHandler { ``` ::: +
+ ## Run it ... for example, from your command line in the root directory of your "Hello World": @@ -143,7 +144,7 @@ mvn cds:watch ## Consume it ... for example, from your browser:
- { .impl .node} + { .impl .node} { .impl .java} diff --git a/get-started/jumpstart.md b/get-started/jumpstart.md index fcf13503e..25abde7bd 100644 --- a/get-started/jumpstart.md +++ b/get-started/jumpstart.md @@ -60,7 +60,7 @@ If not, download and run the appropriate installer from [git-scm.com](https://gi ### 5. Install Java & Maven -- **If you want to go for CAP Java projects**, ensure you have [Java](https://sapmachine.io) and [Maven](https://maven.apache.org/download.cgi) installed. +- **If you want to go for CAP Java projects**, ensure you have [Java](https://sapmachine.io) and [Maven](https://maven.apache.org/download.cgi) installed [as described here](https://cap.cloud.sap/docs/java/getting-started#local). ### 6. Install Visual Studio Code diff --git a/guides/authorization.md b/guides/authorization.md index f9ba9bdaf..62196b506 100644 --- a/guides/authorization.md +++ b/guides/authorization.md @@ -1,6 +1,6 @@ --- index: 44 -layout: cookbook +# layout: cookbook label: Authorization synopsis: > This guide explains how to restrict access to data by adding respective declarations to CDS models, which are then enforced in service implementations. diff --git a/guides/data-privacy/assets/pdmApplication.png b/guides/data-privacy/assets/pdmApplication.png index 5eea994e8..45bffc0b4 100644 Binary files a/guides/data-privacy/assets/pdmApplication.png and b/guides/data-privacy/assets/pdmApplication.png differ diff --git a/guides/data-privacy/audit-log.md b/guides/data-privacy/audit-log.md new file mode 100644 index 000000000..e11dfad9b --- /dev/null +++ b/guides/data-privacy/audit-log.md @@ -0,0 +1,182 @@ +--- +# layout: cookbook +shorty: Audit Log +synopsis: > + Enable and use audit-log capabilities with your CAP application. +breadcrumbs: + - Cookbook + - Data Privacy + - Audit Logging +status: released +--- + + +# Audit Logging with CAP + +{{ $frontmatter.synopsis }} + +## Introduction + +This section deals with Audit Logging for reading sensitive data and changes to personal data. As a prerequisite, you have [indicated entities and elements in your domain model, which will contain personal data](introduction#indicate-privacy). + + + +In CAP, audit logging can be handled mostly automatically by adding certain annotations to your business entity definitions and adding some configuration to your project. + +::: warning _❗ Data Subject and Data Object_
+For each audit log on a data object (like a Sales Order) a valid data subject (like a Customer) is needed. +The application has to clarify that this link between data object and data subject - which is typically induced by an annotation like +`Customer @PersonalData.FieldSemantics : 'DataSubjectID';` - is never broken. Thus, semantically correct audit logs can only be written on top of a semantically correct built application. + +Make sure that the data subject is a valid CAP entity, otherwise the metadata-driven automatism will not work. +::: + +## About the Audited Object + +### Data Subject + +In our case `Customers` is the main master data entity with the semantics of 'Data Subject'. + +```cds +using { cuid, Country } from '@sap/cds/common'; + + entity Customers : cuid, managed { + email : String; + firstName : String; + lastName : String; + creditCardNo : String; + dateOfBirth : Date; + postalAddress : Association to CustomerPostalAddress on postalAddress.Customer = $self; +} + +``` + +This entity is annotated in the _db/data-privacy.cds_ file. + +```cds + +annotate bookshop.Customers with @PersonalData : { + DataSubjectRole : 'Customer', + EntitySemantics : 'DataSubject' +} +{ + ID @PersonalData.FieldSemantics : 'DataSubjectID'; + emailAddress @PersonalData.IsPotentiallyPersonal; + firstName @PersonalData.IsPotentiallyPersonal; + lastName @PersonalData.IsPotentiallyPersonal; + creditCardNo @PersonalData.IsPotentiallySensitive; + dateOfBirth @PersonalData.IsPotentiallyPersonal; +} + +annotate bookshop.Customers with @AuditLog.Operation : { + Read : true, + Insert : true, + Update : true, + Delete : true +}; + +``` + +Here we again have the four levels of annotations as already described in the chapter [Indicate Personal Data in Your Domain Model](introduction#indicate-privacy). + +When you've annotated your (business) entity like this, the audit logs for read access and data modifications will be written automatically by the underlying CAP framework. + +In the context of audit logging, the `@PersonalData.IsPotentiallyPersonal` field-level annotation is relevant for inducing audit logs for _Insert_, _Update_, and _Delete_, whereas the `@PersonalData.IsPotentiallySensitive` annotation is relevant for _Read_ access audit logs. + +::: warning _Warning_ +The `@PersonalData.IsPotentiallySensitive` annotation induces an audit log for each and every _Read_ access. +--- Only use this annotation in [relevant cases](https://ec.europa.eu/info/law/law-topic/data-protection/reform/rules-business-and-organisations/legal-grounds-processing-data/sensitive-data/what-personal-data-considered-sensitive_en). +--- Avoid unnecessary logging activities in your application. +::: + + +### Data Subject Details + +In the first example, the audited object was identical to the data subject, but this is not always the case. + +In many cases you have additional master data describing more details of the data subject stored in a separate entity. +In our terminology this has the semantics 'Data Subject Details'. + +In our example we have the additional entity `CustomerPostalAddress` which contains additional master data belonging to a certain 'Customer', but which are stored in a separate entity, for better clarity or better separation of concerns. + +```cds + +entity CustomerPostalAddress : cuid, managed { + Customer : Association to one Customers; + street : String(128); + town : String(128); + country : Country; + someOtherField : String(128); +}; + +``` + +This entity is annotated in the _db/data-privacy.cds_ file. + +```cds +annotate bookshop.CustomerPostalAddress with @PersonalData : { + DataSubjectRole : 'Customer', + EntitySemantics : 'DataSubjectDetails' +} +{ + Customer @PersonalData.FieldSemantics : 'DataSubjectID'; + street @PersonalData.IsPotentiallyPersonal; + town @PersonalData.IsPotentiallyPersonal; + country @PersonalData.IsPotentiallyPersonal; +} + +annotate bookshop.CustomerPostalAddress with @AuditLog.Operation : { + Read : true, + Insert : true, + Update : true, + Delete : true +}; + +``` + +Very similarly to the section on 'Data Subject' this entity is as well annotated in four levels. +More details on these annotations can be found in the chapter [Indicate Personal Data in Your Domain Model](introduction#indicate-privacy). + +### Transactional Data + +In the section on 'Data Subject' and 'Data Subject Details' we have seen, how to annotate the master data entities carrying the semantical information of the 'Data Subject'. + +Now we have a look at classical transactional data. + +In the Personal Data Terminology all transactional data like 'Sales Orders', 'Shipments', 'Payments' are summarizes under the classification 'Other', which means they are relevant for Data Privacy, but they are neither 'Data Subject' nor 'Data Subject Details'. +More details on this Terminology can be found in the chapter [Indicate Personal Data in Your Domain Model](introduction#indicate-privacy). + +In our example we have the entity 'Orders' + +```cds +entity Orders : cuid, managed { + OrderNo : String @title:'Order Number'; //> readable key + Items : Composition of many OrderItems on Items.parent = $self; + currency : Currency; + Customer : Association to Customers; + personalComment : String; +} +``` + +To ensure proper audit logging we annotate using the usual four levels as described in the chapter [Indicate Personal Data in Your Domain Model](introduction#indicate-privacy). + +```cds +annotate bookshop.Orders with @PersonalData.EntitySemantics : 'Other' +{ + ID @PersonalData.FieldSemantics : 'ContractRelatedID'; + Customer @PersonalData.FieldSemantics : 'DataSubjectID'; + personalComment @PersonalData.IsPotentiallyPersonal; +} +annotate bookshop.Orders with @AuditLog.Operation : { + Read : true, + Insert : true, + Update : true, + Delete : true +}; +``` + +Finally, we annotate all standard operations (`Read`, `Insert`, `Update`, `Delete`) as relevant for the audit log - which should be the default case for most of the relevant business entities. + +
+ + \ No newline at end of file diff --git a/guides/data-privacy/index.md b/guides/data-privacy/index.md index ae04faa77..e751d445b 100644 --- a/guides/data-privacy/index.md +++ b/guides/data-privacy/index.md @@ -4,14 +4,14 @@ label: Data Privacy synopsis: > CAP helps application projects to comply with data privacy regulations using SAP Business Technology Platform (BTP) services. permalink: guides/data-privacy/ -layout: cookbook +# layout: cookbook status: released --- # Managing Data Privacy -
+{{ $frontmatter.synopsis }} The following guides give detailed information to each of these options... @@ -22,4 +22,3 @@ import { data as pages } from './index.data.js'
- diff --git a/guides/data-privacy/introduction.md b/guides/data-privacy/introduction.md index 2da840f64..14d48aed5 100644 --- a/guides/data-privacy/introduction.md +++ b/guides/data-privacy/introduction.md @@ -1,5 +1,5 @@ --- -layout: cookbook +# layout: cookbook shorty: Basics synopsis: > This guide explains the basic annotations related to data privacy. @@ -13,7 +13,7 @@ status: released # Basics of Data Privacy -
+{{ $frontmatter.synopsis }} diff --git a/guides/data-privacy/pdm.md b/guides/data-privacy/pdm.md index e9f3ef980..6a0e6f268 100644 --- a/guides/data-privacy/pdm.md +++ b/guides/data-privacy/pdm.md @@ -1,5 +1,5 @@ --- -layout: cookbook +# layout: cookbook label: Personal Data Management shorty: PDM synopsis: > @@ -16,7 +16,7 @@ status: released # Personal Data Management -
+{{ $frontmatter.synopsis }} ::: warning _❗ To follow this cookbook hands-on you need an enterprise account._ The SAP Personal Data Manager service is currently only available for [enterprise accounts](https://discovery-center.cloud.sap/missiondetail/3019/3297/). An entitlement in trial accounts is not possible. diff --git a/guides/databases-h2.md b/guides/databases-h2.md index d73b1928c..aaa4e53d0 100644 --- a/guides/databases-h2.md +++ b/guides/databases-h2.md @@ -1,18 +1,31 @@ --- -#status: released +status: released impl-variants: true --- -
-# Using H2 for Development in CAP Java {.impl .java} + +# Using H2 for Development in CAP Java For local development and testing, CAP Java supports the [H2](https://www.h2database.com/) database, which can be configured to run in-memory. -[Learn more about features and limitations of H2](../java/persistence-services#h2){.learn-more} +[Learn more about features and limitations of using CAP with H2](../java/persistence-services#h2){.learn-more} + +
+ +::: warning +Not supported for CAP Node.js. +::: + +
+ + +
[[toc]] +
+ ## Setup & Configuration {.impl .java} ### Using the Maven Archetype {.impl .java} @@ -49,6 +62,3 @@ CAP supports most of the major features on H2: * [Predicate Functions](../java/query-api#predicate-functions) [Learn about features and limitations of H2](../java/persistence-services#h2){.learn-more} - -
- diff --git a/guides/databases-hana.md b/guides/databases-hana.md index 7d59f0651..b5791fe9c 100644 --- a/guides/databases-hana.md +++ b/guides/databases-hana.md @@ -1,4 +1,5 @@ --- +status: released impl-variants: true --- @@ -8,7 +9,8 @@ impl-variants: true [[toc]] -[SAP HANA Cloud](https://www.sap.com/products/technology-platform/hana.html) is supported as the CAP standard database and recommended for productive use with full support for schema evolution and multitenancy. + +[SAP HANA Cloud](https://www.sap.com/products/technology-platform/hana.html) is supported as the CAP standard database and recommended for productive use with full support for schema evolution and multitenancy. ::: warning @@ -166,7 +168,6 @@ In addition to the generated HDI artifacts, you can add custom ones by adding ac [cds] - done > wrote output to: ... gen/db/src/sap.capire.bookshop.Books.hdbindex //[!code focus] - ``` @@ -414,7 +415,7 @@ There are cases where you have to resolve or refactor the generated statements, Example: -``` +```txt >>>> Manual resolution required - DROP statements causing data loss are disabled >>>> by default. >>>> You may either: @@ -476,7 +477,7 @@ ALTER TABLE E ALTER (text NVARCHAR(100) FUZZY SEARCH INDEX ON); It's important to understand that during deployment new migration versions will be applied on the existing database schema. If the resulting schema doesn't match the schema as defined by the TABLE statement, deployment fails and any changes are rolled-back. In consequence, when removing or replacing an existing `@sql.append` annotation, the original ALTER statements need to be undone. As the required statements can't automatically be determined, manual resolution is required. The CDS build generates comments starting with `>>>>` in order to provide some guidance and enforce manual resolution. Generated file with comments: -``` +```txt == migration=3 >>>>> Manual resolution required - insert ALTER statement(s) as described below. >>>>> After manual resolution delete all lines starting with >>>>> @@ -545,17 +546,27 @@ Yet, if you need to support initial data with user changes, you can use the `inc ### Undeploying Artifacts -As documented in [HDI Deployer docs](https://help.sap.com/docs/HANA_CLOUD_DATABASE/c2b99f19e9264c4d9ae9221b22f6f589/ebb0a1d1d41e4ab0a06ea951717e7d3d.html), a HDI deployment by default never deletes artifacts. So if you remove an entity, or csv files, the respective tables and content will remain in the database. +As documented in the [HDI Deployer docs](https://help.sap.com/docs/HANA_CLOUD_DATABASE/c2b99f19e9264c4d9ae9221b22f6f589/ebb0a1d1d41e4ab0a06ea951717e7d3d.html), a HDI deployment by default never deletes artifacts. So if you remove an entity, or CSV files, the respective tables and content will remain in the database. + +By default, `cds add hana` will create an `undeploy.json` like this: + +::: code-group +```json [db/src/undeploy.json] +[ + "src/gen/**/*.hdbview", + "src/gen/**/*.hdbindex", + "src/gen/**/*.hdbconstraint" +] +``` -Add an `undeploy.json` file in folder `db/src` with content like this: +If you need to remove deployed CSV files, also add this entry: ::: code-group -```sql [db/src/undeploy.json] +```json [db/src/undeploy.json] [ - "src/gen/**/*.hdbconstraint", - "src/gen/**/*.hdbtabledata", ... + "src/gen/**/*.hdbtabledata" ] ``` diff --git a/guides/databases-postgres.md b/guides/databases-postgres.md index e517e8b97..3e699b4c3 100644 --- a/guides/databases-postgres.md +++ b/guides/databases-postgres.md @@ -1,11 +1,562 @@ --- -#status: released +status: released +impl-variants: true --- # Using PostgreSQL -Run this to use [PostgreSQL](https://www.postgresql.org/) as an alternative for production: +
+ +This guide focuses on the new PostgreSQL Service provided through *[@cap-js/postgres](https://www.npmjs.com/package/@cap-js/postgres)*, which is based on the same new database services architecture as the new [SQLite Service](databases-sqlite). This architecture brings significantly enhanced feature sets and feature parity, as documented in the [*Features* section of the SQLite guide](databases-sqlite#features). + +*Learn about migrating from the former `cds-pg` in the [Migration](#migration) chapter.*{.learn-more} + +
+ +
+ +CAP Java SDK is tested on [PostgreSQL](https://www.postgresql.org/) 15. Most CAP features are supported on PostgreSQL. + +[Learn more about features and limitations of using CAP with PostgreSQL](../java/persistence-services#postgresql){.learn-more} + +
+ +[[toc]] + + +## Setup & Configuration + +
+ +Run this to use [PostgreSQL](https://www.postgresql.org/) for production: + +
+ +
+ +To run CAP Java on PostgreSQL, add a Maven dependency to the PostgreSQL feature in `srv/pom.xml`: + +```xml + + com.sap.cds + cds-feature-postgresql + runtime + +``` + +In order to use the CDS tooling with PostgreSQL, you also need to install the module `@cap-js/postgres`: + +
```sh npm add @cap-js/postgres ``` + +
+ +After that, you can use the `cds deploy` command to [deploy](#using-cds-deploy) to a PostgreSQL database or to [create a DDL script](#deployment-using-liquibase) for PostgreSQL. + +
+ +### Auto-Wired Configuration {.impl .node} + +The `@cap-js/postgres` package uses `cds-plugin` technique to auto-configure your application and use a PostgreSQL database for production. + +You can inspect the effective configuration using `cds env`: + +```sh +cds env requires.db --for production +``` + +Output: + +```js +{ + impl: '@cap-js/postgres', + dialect: 'postgres', + kind: 'postgres' +} +``` + +[See also the general information on installing database packages](databases#setup-configuration){.learn-more} + +## Provisioning a DB Instance + +To connect to a PostgreSQL offering from the cloud provider in Production, leverage the [PostgreSQL on SAP BTP, hyperscaler option](https://discovery-center.cloud.sap/serviceCatalog/postgresql-hyperscaler-option). + +For local development and testing convenience, you can run PostgreSQL in a [docker container](#using-docker). + +### Using Docker + +You can use Docker to run a PostgreSQL database locally as follows: + +1. Install and run [Docker Desktop](https://www.docker.com) + +2. Create a file like that: + ::: code-group + + ```yaml [pg.yml] + services: + db: + image: postgres:alpine + environment: { POSTGRES_PASSWORD: postgres } + ports: [ '5432:5432' ] + restart: always + ``` + + ::: + +3. Create and run the docker container: + + ```sh + docker-compose -f pg.yml up -d + ``` + +
+ +::: tip +With the introduction of [Testcontainers support](https://spring.io/blog/2023/06/23/improved-testcontainers-support-in-spring-boot-3-1) in Spring Boot 3.1, you can create PostgreSQL containers on the fly for local development or testing purposes. +::: + +
+ +## Service Bindings + +You need a service binding to connect to the PostgreSQL database. + +In the cloud, use given techniques to bind a cloud-based instance of PostgreSQL to your application. + +
+ +For local development provide the credentials using a suitable [`cds env`](../node.js/cds-env) technique, like one of the following. + +
+ +### Configure Connection Data {.impl .java} + +If a PostgreSQL service binding exists, the corresponding `DataSource` is auto-configured. + +You can also explicitly [configure the connection data](../java/persistence-services#postgres-connection) of your PostgreSQL database in the _application.yaml_ file. +If you run the PostgreSQL database in a [docker container](#using-docker) your connection data might look like this: + +```yaml +spring: + config.activate.on-profile: postgres-docker + datasource: + url: jdbc:postgresql://localhost:5432/postgres + username: postgres + password: postgres + driver-class-name: org.postgresql.Driver +``` +To start the application with the new profile `postgres-docker`, the `spring-boot-maven-plugin` can be used: `mvn spring-boot:run -Dspring-boot.run.profiles=postgres-docker`. +Learn more about the [configuration of a PostgreSQL database](../java/persistence-services#postgresql-1){ .learn-more} + +### Service Bindings for CDS Tooling {.impl .java} + +#### Using Defaults with `[pg]` Profile {.impl .java} + +`@cds-js/postgres` comes with a set of default credentials under the profile `[pg]` that matches the defaults used in the [docker setup](#using-docker). So, if you stick to these defaults you can skip to deploying your database with: + +```sh +cds deploy --profile pg +``` + +#### In Your Private `.cdsrc-private.json` {.impl .java} + +If you don't use the default credentials and want to use just `cds deploy`, you need to configure the service bindings (connection data) for the CDS tooling. Add the connection data to your private `.cdsrc-private.json`: + +```json +{ + "requires": { + "db": { + "kind": "postgres", + "credentials": { + "host": "localhost", "port": 5432, + "user": "postgres", + "password": "postgres", + "database": "postgres" + } + } + } +} +``` + +### Configure Service Bindings {.impl .node} + +#### Using Defaults with `[pg]` Profile + +The `@cds-js/postgres` comes with default credentials under profile `[pg]` that match the defaults used in the [docker setup](#using-docker). So, in case you stick to these defaults you can skip the next sections and just go ahead, deploy your database: + +```sh +cds deploy --profile pg +``` + +Run your application: + +```sh +cds watch --profile pg +``` + +Learn more about that in the [Deployment](#deployment) chapter below.{.learn-more} + + + +#### In Your private `~/.cdsrc.json` + +Add it to your private `~/.cdsrc.json` if you want to use these credentials on your local machine only: + +::: code-group + +```json [~/.cdsrc.json] +{ + "requires": { + "db": { + "[pg]": { + "kind": "postgres", + "credentials": { + "host": "localhost", "port": 5432, + "user": "postgres", + "password": "postgres", + "database": "postgres" + } + } + } + } +} +``` + +::: + +#### In Project `.env` Files + +Alternatively, use a `.env` file in your project's root folder if you want to share the same credentials with your team: + +::: code-group + +```properties [.env] +cds.requires.db.[pg].kind = postgres +cds.requires.db.[pg].credentials.host = localhost +cds.requires.db.[pg].credentials.port = 5432 +cds.requires.db.[pg].credentials.user = postgres +cds.requires.db.[pg].credentials.password = postgres +cds.requires.db.[pg].credentials.database = postgres +``` + +::: + +::: tip Using Profiles + +The previous configuration examples use the [`cds.env` profile](../node.js/cds-env#profiles) `[pg]` to allow selectively testing with PostgreSQL databases from the command line as follows: + +```sh +cds watch --profile pg +``` + +The profile name can be freely chosen, of course. + +::: + + + +## Deployment + +### Using `cds deploy` + +Deploy your database as usual with that: + +```sh +cds deploy +``` + +Or with that if you used profile `[pg]` as introduced in the setup chapter above: + +```sh +cds deploy --profile pg +``` + +### With a Deployer App + +When deploying to Cloud Foundry, this can be accomplished by providing a simple deployer app, which you can construct as follows: + +1. Create a new folder named `gen/pg/db`: + ```sh + mkdir -p gen/pg/db + ``` + +2. Generate a precompiled cds model: + ```sh + cds compile '*' > gen/pg/db/csn.json + ``` + +3. Add required `.csv` files, for example: + ```sh + cp -r db/data gen/pg/db/data + ``` + +4. Add a *package.json* to `gen/pg` with this content: + ::: code-group + ```json [gen/pg/package.json] + { + "dependencies": { + "@sap/cds": "*", + "@cap-js/postgres": "*" + }, + "scripts": { + "start": "cds-deploy" + } + } + ``` + ::: + > **Note the dash in `cds-deploy`**, which is required as we don't use `@cds-dk` for deployment and runtime, so the `cds` CLI executable isn't available. + +5. Finally, package and deploy that, for example using [MTA-based deployment](deployment/to-cf#build-mta). + +## Automatic Schema Evolution { #schema-evolution } + +When redeploying after you changed your CDS models, like adding fields, automatic schema evolution is applied. Whenever you run `cds deploy` (or `cds-deploy`) it executes these steps: + +1. Read a CSN of a former deployment from table `cds_model`. +2. Calculate the **delta** to current model. +3. Generate and run DDL statements with: + - `CREATE TABLE` statements for new entities + - `CREATE VIEW` statements for new views + - `ALTER TABLE` statements for entities with new or changed elements + - `DROP & CREATE VIEW` statements for views affected by changed entities +4. Fill in initial data from provided _.csv_ files using `UPSERT` commands. +5. Store a CSN representation of the current model in `cds_model`. + + +> You can disable automatic schema evolution, if necessary, by setting `cds.requires.db.schema_evolution = false`. + +### Limitations + +Automatic schema evolution only allows changes without potential data loss. + +#### Allowed{.good} + +- Adding entities and elements +- Increasing the length of Strings +- Increasing the size of Integers + +#### Disallowed{.bad} + +- Removing entities or elements +- Changes to primary keys +- All other type changes + +For example the following type changes are allowed: + +```cds +entity Foo { + anInteger : Int64; // from former: Int32 + aString : String(22); // from former: String(11) +} +``` + +::: tip + +If you need to apply such disallowed changes during development, just drop and re-create your database, for example by killing it in docker and re-create it using the `docker-compose` command, [see Using Docker](#using-docker). + +::: + + + +### Dry-Run Offline + +We can use `cds deploy` with option `--dry` to simulate and inspect how things work. + +1. Capture your current model in a CSN file: + + ```sh + cds deploy --dry --model-only > cds-model.csn + ``` + +2. Change your models, for example in *[cap/samples/bookshop/db/schema.cds](https://github.com/SAP-samples/cloud-cap-samples/blob/main/bookshop/db/schema.cds)*: + + ```cds + entity Books { ... + title : localized String(222); //> increase length from 111 to 222 + foo : Association to Foo; //> add a new relationship + bar : String; //> add a new element + } + entity Foo { key ID: UUID } //> add a new entity + ``` + +3. Generate delta DDL script: + + ```sh + cds deploy --dry --delta-from cds-model.csn > delta.sql + ``` + +4. Inspect the generated SQL script, which should look like this: + ::: code-group + + ```sql [delta.sql] + -- Drop Affected Views + DROP VIEW localized_CatalogService_ListOfBooks; + DROP VIEW localized_CatalogService_Books; + DROP VIEW localized_AdminService_Books; + DROP VIEW CatalogService_ListOfBooks; + DROP VIEW localized_sap_capire_bookshop_Books; + DROP VIEW CatalogService_Books_texts; + DROP VIEW AdminService_Books_texts; + DROP VIEW CatalogService_Books; + DROP VIEW AdminService_Books; + + -- Alter Tables for New or Altered Columns + ALTER TABLE sap_capire_bookshop_Books ALTER title TYPE VARCHAR(222); + ALTER TABLE sap_capire_bookshop_Books_texts ALTER title TYPE VARCHAR(222); + ALTER TABLE sap_capire_bookshop_Books ADD foo_ID VARCHAR(36); + ALTER TABLE sap_capire_bookshop_Books ADD bar VARCHAR(255); + + -- Create New Tables + CREATE TABLE sap_capire_bookshop_Foo ( + ID VARCHAR(36) NOT NULL, + PRIMARY KEY(ID) + ); + + -- Re-Create Affected Views + CREATE VIEW AdminService_Books AS SELECT ... FROM sap_capire_bookshop_Books AS Books_0; + CREATE VIEW CatalogService_Books AS SELECT ... FROM sap_capire_bookshop_Books AS Books_0 LEFT JOIN sap_capire_bookshop_Authors AS author_1 O ... ; + CREATE VIEW AdminService_Books_texts AS SELECT ... FROM sap_capire_bookshop_Books_texts AS texts_0; + CREATE VIEW CatalogService_Books_texts AS SELECT ... FROM sap_capire_bookshop_Books_texts AS texts_0; + CREATE VIEW localized_sap_capire_bookshop_Books AS SELECT ... FROM sap_capire_bookshop_Books AS L_0 LEFT JOIN sap_capire_bookshop_Books_texts AS localized_1 ON localized_1.ID = L_0.ID AND localized_1.locale = session_context( '$user.locale' ); + CREATE VIEW CatalogService_ListOfBooks AS SELECT ... FROM CatalogService_Books AS Books_0; + CREATE VIEW localized_AdminService_Books AS SELECT ... FROM localized_sap_capire_bookshop_Books AS Books_0; + CREATE VIEW localized_CatalogService_Books AS SELECT ... FROM localized_sap_capire_bookshop_Books AS Books_0 LEFT JOIN localized_sap_capire_bookshop_Authors AS author_1 O ... ; + CREATE VIEW localized_CatalogService_ListOfBooks AS SELECT ... FROM localized_CatalogService_Books AS Books_0; + ``` + + ::: + + > **Note:** If you use SQLite, ALTER TYPE commands are not necessary and so, are not supported, as SQLite is essentially typeless. + +## Deployment Using Liquibase { .impl .java } + +You can also use [Liquibase](https://www.liquibase.org/) to control when, where, and how database changes are deployed. Liquibase lets you define database changes [in an SQL file](https://docs.liquibase.com/change-types/sql-file.html), use `cds deploy` to quickly generate DDL scripts which can be used by Liquibase. + +Add a Maven dependency to Liquibase in `srv/pom.xml`: + +```xml + + org.liquibase + liquibase-core + runtime + +``` + +Once `liquibase-core` is on the classpath, [Spring runs database migrations](https://docs.spring.io/spring-boot/docs/current/reference/html/howto.html#howto.data-initialization.migration-tool.liquibase) automatically on application startup and before your tests run. + +### ① Initial Schema Version + +Once you're ready to release an initial version of your database schema, you can create a DDL file that defines the initial database schema. Create a `db/changelog` subfolder under `srv/src/main/resources`, place the Liquibase _change log_ file as well as the DDL scripts for the schema versions here. The change log is defined by the [db/changelog/db.changelog-master.yml](https://docs.liquibase.com/concepts/changelogs/home.html) file: + +```yml +databaseChangeLog: + - changeSet: + id: 1 + author: me + changes: + - sqlFile: + dbms: postgresql + path: db/changelog/v1/model.sql +``` + +Use `cds deploy` to create the _v1/model.sql_ file: + +```sh +cds deploy --profile pg --dry > srv/src/main/resources/db/changelog/v1/model.sql +``` +Finally, store the CSN file, which corresponds to this schema version: + +```sh +cds deploy --model-only --dry > srv/src/main/resources/db/changelog/v1/model.csn +``` + +The CSN file is needed as an input to compute the delta DDL script for the next change set. + +If you start your application with `mvn spring-boot:run` Liquibase initializes the database schema to version `v1`, unless it has already been initialized. + +::: warning +Don't change the _model.sql_ after it has been deployed by Liquibase as the [checksum](https://docs.liquibase.com/concepts/changelogs/changeset-checksums.html) of the file is validated. These files should be checked into your version control system. Follow step ② to make changes. +::: + +### ② Schema Evolution { #schema-evolution-with-liquibase } + +If changes of the CDS model require changes on the database, you can create a new change set that captures the necessary changes. + +Use `cds deploy` to compute the delta DDL script based on the previous model versions (_v1/model.csn_) and the current model. Write the diff into a _v2/delta.sql_ file: + +```sh +cds deploy --profile pg --dry --delta-from srv/src/main/resources/db/changelog/v1/model.csn > \ + srv/src/main/resources/db/changelog/v2/model.sql +``` + +Next, add a corresponding change set in the _changelog/db.changelog-master.yml_ file: + +```yml +databaseChangeLog: + - changeSet: + id: 1 + author: me + changes: + - sqlFile: + dbms: postgresql + path: db/changelog/v1/model.sql + - changeSet: + id: 2 + author: me + changes: + - sqlFile: + dbms: postgresql + path: db/changelog/v2/model.sql +``` + +Finally, store the CSN file, which corresponds to this schema version: + +```sh +cds deploy --model-only --dry > srv/src/main/resources/db/changelog/v2/model.csn +``` + +If you now start the application, Liquibase executes all change sets, which haven't yet been deployed to the database. + +For further schema versions, repeat step ②. + +## Migration { .impl .node } + +Thanks to CAP's database-agnostic cds.ql API, we're confident that the new PostgreSQL service comes without breaking changes. Nevertheless, please check the instructions in the [SQLite Migration guide](databases-sqlite#migration), with by and large applies also to the new PostgreSQL service. + +### `cds deploy --model-only` + +Not a breaking change, but definitely required to migrate former `cds-pg` databases, is to prepare it for schema evolution. + +To do so run `cds deploy` once with the `--model-only` flag: + +```sh +cds deploy --model-only +``` + +This will...: + +- Create the `cds_model` table in your database. +- Fill it with the current model obtained through `cds compile '*'`. + +::: warning IMPORTANT: + +Your `.cds` models are expected to reflect the deployed state of your database. + +::: + +### With Deployer App + +When you have a SaaS application, upgrade all your tenants using the [deployer app](#with-deployer-app) with CLI option `--model-only` added to the start script command of your *package.json*. After having done that, don't forget to remove the `--model-only` option from the start script, to activate actual schema evolution. + + + +## MTX Support + +::: warning + +[Multitenancy](../guides/multitenancy/) and [extensibility](../guides/extensibility/) aren't yet supported on PostgreSQL. + +::: diff --git a/guides/databases-sqlite.md b/guides/databases-sqlite.md index 5479f05b1..62c4de0d2 100644 --- a/guides/databases-sqlite.md +++ b/guides/databases-sqlite.md @@ -17,7 +17,8 @@ This guide focuses on the new SQLite Service provided through *[@cap-js/sqlite](
-[Learn more about features and limitations of SQlite.](../java/persistence-services#sqlite){.learn-more} +[Learn more about features and limitations of using CAP with SQlite](../java/persistence-services#sqlite){.learn-more} +
@@ -29,12 +30,32 @@ This guide focuses on the new SQLite Service provided through *[@cap-js/sqlite](
-Run this to use SQLite during development: +Run this to use SQLite for development: ```sh npm add @cap-js/sqlite -D ``` +### Auto-wired Configuration {.impl .node} + +The `@cap-js/sqlite` uses `cds-plugin` technique to auto-configure your application to use an in-memory SQLite database for development. + +You can inspect the effective configuration using `cds env`: + +```sh +cds env requires.db +``` + +Output: + +```js +{ + impl: '@cap-js/sqlite', + credentials: { url: ':memory:' }, + kind: 'sqlite' +} +``` + [See also the general information on installing database packages](databases#setup-configuration){.learn-more}
@@ -60,12 +81,14 @@ To use SQLite, add a Maven dependency to the SQLite JDBC driver: The further configuration depends on whether you run SQLite as an [in-memory database](#in-memory-databases) or as a [file-based](#persistent-databases) database. +## Deployment ### In-Memory Databases
-Installing `@cap-js/sqlite`, as described previously, automatically configures your application to use an in-memory SQLite database. For example, you can see this in the log output when starting your application, with `cds watch`: + +As stated above `@cap-js/sqlite` uses an in-memory SQLite database by default. For example, you can see this in the log output when starting your application, with `cds watch`: ```log ... @@ -79,29 +102,18 @@ Installing `@cap-js/sqlite`, as described previously, automatically configures y ... ``` -You can inspect the effective configuration using `cds env`: - -```sh -cds env requires.db -``` - -Output: - -```js -{ - impl: '@cap-js/sqlite', - credentials: { url: ':memory:' }, - kind: 'sqlite' -} -``` +::: tip +Using in-memory databases is most recommended for test drives and for test pipelines. +:::
-The database content is stored in-memory. [Configure the build](../java/persistence-services#initial-database-schema-1) to create an initial _schema.sql_ file for SQLite using `cds deploy --to sqlite --dry > srv/src/main/resources/schema.sql`. + +The database content is stored in-memory. [Configure the build](../java/persistence-services#initial-database-schema-1) to create an initial _schema.sql_ file for SQLite using `cds deploy --to sqlite --dry > srv/src/main/resources/schema.sql`. Finally, configure the DB connection in the non-productive `default` profile: @@ -118,9 +130,6 @@ spring: hikari: maximum-pool-size: 1 max-lifetime: 0 -cds: - sql: - supportedLocales: "*" ``` [Learn how to configure an in-memory SQLite database](../java/persistence-services#in-memory-storage){.learn-more} @@ -136,12 +145,14 @@ If possible, all common scenarios should be covered by shortcuts only.
+ You can also use persistent SQLite databases. Follow these steps to do so:
+ You can also use persistent SQLite databases. In this case, the schema is initialized by `cds deploy` and not by Spring. Follow these steps:
@@ -149,6 +160,7 @@ You can also use persistent SQLite databases. In this case, the schema is initia 1. Specify a database filename in your `db` configuration as follows: ::: code-group + ```json [package.json] { "cds": { "requires": { "db": { @@ -157,9 +169,11 @@ You can also use persistent SQLite databases. In this case, the schema is initia } }}} ``` + ::: 2. Run `cds deploy`: + ```sh cds deploy ``` @@ -172,6 +186,7 @@ This will...:
+ With that in place, when starting the server it will use this prepared database instead of bootstrapping an in-memory one: ```log @@ -184,6 +199,7 @@ With that in place, when starting the server it will use this prepared database
+ Finally, configure the DB connection - ideally in a dedicated `sqlite` profile: ```yaml @@ -195,9 +211,6 @@ spring: driver-class-name: org.sqlite.JDBC hikari: maximum-pool-size: 1 -cds: - sql: - supportedLocales: "*" ``` [Learn how to configure a file based SQLite database](../java/persistence-services#file-based-storage){.learn-more} @@ -214,13 +227,14 @@ Remember to always re-deploy your database whenever you made changes to your mod When running `cds deploy` repeatedly it will always drop-create all tables and views. This is **most appropriate for development** as schema changes are very frequent and broad during development. -## Schema Evolution +### Schema Evolution While drop-create is most appropriate for development, it isn't for database upgrades in production, as all customer data would be lost. To avoid this `cds deploy` also supports automatic schema evolution, which you can use as follows... 1. Enable automatic schema evolution in your `db` configuration: ::: code-group + ```json [package.json] { "cds": { "requires": { "db": { @@ -230,6 +244,7 @@ While drop-create is most appropriate for development, it isn't for database upg } }}} ``` + ::: 2. Run `cds deploy`: @@ -238,117 +253,13 @@ While drop-create is most appropriate for development, it isn't for database upg cds deploy ``` -Then the following happens: - -1. Read a CSN of a former deployment from table `cds_model`. -2. Calculate the delta to current model. -3. Generate and run SQL DDL statements with: - - `CREATE TABLE` statements for new entities - - `CREATE VIEW` statements for new views - - `ALTER TABLE` statements for entities with new or changed elements - - `DROP & CREATE VIEW` statements for views affected by changed entities -4. Fill in initial data from provided _.csv_ files using `UPSERT` commands. -5. Store a CSN representation of the current model in `cds_model`. - - - -### Dry-Run Offline - -We can use `cds deploy` with option `--dry` to simulate and inspect how things work. - -1. Capture your current model in a CSN file: - ```sh - cds deploy --dry --model-only > cds-model.csn - ``` - -2. Make changes to your models, for example to *[cap/samples/bookshop/db/schema.cds](https://github.com/SAP-samples/cloud-cap-samples/blob/main/bookshop/db/schema.cds)*: - ```cds - entity Books { ... - title : localized String(222); //> increase length from 111 to 222 - foo : Association to Foo; //> add a new relationship - bar : String; //> add a new element - } - entity Foo { key ID: UUID } //> add a new entity - ``` - -3. Generate delta SQL DDL script: - ```sh - cds deploy --dry --delta-from cds-model.csn > delta.sql - ``` - -4. Inspect the generated SQL script, which should look like this: - ::: code-group - - ```sql [delta.sql] - -- Drop Affected Views - DROP VIEW localized_CatalogService_ListOfBooks; - DROP VIEW localized_CatalogService_Books; - DROP VIEW localized_AdminService_Books; - DROP VIEW CatalogService_ListOfBooks; - DROP VIEW localized_sap_capire_bookshop_Books; - DROP VIEW CatalogService_Books_texts; - DROP VIEW AdminService_Books_texts; - DROP VIEW CatalogService_Books; - DROP VIEW AdminService_Books; - - -- Alter Tables for New or Altered Columns - -- ALTER TABLE sap_capire_bookshop_Books ALTER title TYPE NVARCHAR(222); - -- ALTER TABLE sap_capire_bookshop_Books_texts ALTER title TYPE NVARCHAR(222); - ALTER TABLE sap_capire_bookshop_Books ADD foo_ID NVARCHAR(36); - ALTER TABLE sap_capire_bookshop_Books ADD bar NVARCHAR(255); - - -- Create New Tables - CREATE TABLE sap_capire_bookshop_Foo ( - ID NVARCHAR(36) NOT NULL, - PRIMARY KEY(ID) - ); - - -- Re-Create Affected Views - CREATE VIEW AdminService_Books AS SELECT ... FROM sap_capire_bookshop_Books AS Books_0; - CREATE VIEW CatalogService_Books AS SELECT ... FROM sap_capire_bookshop_Books AS Books_0 LEFT JOIN sap_capire_bookshop_Authors AS author_1 O ... ; - CREATE VIEW AdminService_Books_texts AS SELECT ... FROM sap_capire_bookshop_Books_texts AS texts_0; - CREATE VIEW CatalogService_Books_texts AS SELECT ... FROM sap_capire_bookshop_Books_texts AS texts_0; - CREATE VIEW localized_sap_capire_bookshop_Books AS SELECT ... FROM sap_capire_bookshop_Books AS L_0 LEFT JOIN sap_capire_bookshop_Books_texts AS localized_1 ON localized_1.ID = L_0.ID AND localized_1.locale = session_context( '$user.locale' ); - CREATE VIEW CatalogService_ListOfBooks AS SELECT ... FROM CatalogService_Books AS Books_0; - CREATE VIEW localized_AdminService_Books AS SELECT ... FROM localized_sap_capire_bookshop_Books AS Books_0; - CREATE VIEW localized_CatalogService_Books AS SELECT ... FROM localized_sap_capire_bookshop_Books AS Books_0 LEFT JOIN localized_sap_capire_bookshop_Authors AS author_1 O ... ; - CREATE VIEW localized_CatalogService_ListOfBooks AS SELECT ... FROM localized_CatalogService_Books AS Books_0; - ``` - - ::: - - > **Note:** ALTER TYPE commands are neither necessary nor supported by SQLite, as SQLite is essentially typeless. - - -### Limitations -Automatic schema evolution only allows changes without potential data loss. +[Learn more about automatic schema evolution in the PostgreSQL guide.
The information in there equally apply to SQLite with persistent databases](databases-postgres#schema-evolution) {.learn-more} -#### Allowed{.good} -- Adding entities and elements -- Increasing the length of Strings -- Increasing the size of Integers -#### Disallowed{.bad} - -- Removing entities or elements -- Changes to primary keys -- All other type changes - -For example the following type changes are allowed: - -```cds -entity Foo { - anInteger : Int64; // from former: Int32 - aString : String(22); // from former: String(11) -} -``` - - - -## Features +## Features
@@ -552,10 +463,10 @@ The old implementation was overly polluted with draft handling. But as draft is Values for elements of type `DateTime` and `Timestamp` are now handled in a consistent way across all new database services, except for timestamp precisions, along these lines: -1. **Allowed input values** — as values you can either provide `Date` objects or ISO 8601 Strings in Zulu time zone, with correct number of fractional digits (0 for DateTimes, up to 7 for Timestamps). -2. **Comparisons** — comparing DateTime with DataTime elements is possible with plain `=`, `<`, `>`, `<=`, `>=` operators, as well as Timestamp with Timestamp elements. When comparing with values, the values have to be provided as stated above. +1. **Allowed input values** — as values you can either provide `Date` objects or ISO 8601 Strings in Zulu time zone, with correct number of fractional digits (0 for DateTimes, up to 7 for Timestamps). +2. **Comparisons** — comparing DateTime with DataTime elements is possible with plain `=`, `<`, `>`, `<=`, `>=` operators, as well as Timestamp with Timestamp elements. When comparing with values, the values have to be provided as stated above. -**IMPORTANT:** While HANA and PostgreSQL provide native datetime and timestamp types, which allow you to provide arbitrary number of fractional digits. SQLite doesn't and the best we can do is storing such values as ISO Strings. In order to support comparisons, you must ensure to always provide the correct number of digits when ingesting string values. For example: +**IMPORTANT:** While HANA and PostgreSQL provide native datetime and timestamp types, which allow you to provide arbitrary number of fractional digits. SQLite doesn't and the best we can do is storing such values as ISO Strings. In order to support comparisons, you must ensure to always provide the correct number of digits when ingesting string values. For example: ```js await INSERT.into(Books).entries([ @@ -567,13 +478,13 @@ let books = await SELECT('title').from(Books).orderBy('createdAt') console.log(books) //> would return [{title:'B'},{title:'C'},{title:'A'}] ``` -The order is wrong because of the `'Z'` in A being at the wrong position. +The order is wrong because of the `'Z'` in A being at the wrong position. ::: tip Prefer using `Date` objects -Unless the data came in through an OData layer which applies respective data input processing, prefer using Date objects instead of string literals to avoid situations as illustrated above. +Unless the data came in through an OData layer which applies respective data input processing, prefer using Date objects instead of string literals to avoid situations as illustrated above. -For example, the above would be fixed by changing the INSERT to: +For example, the above would be fixed by changing the INSERT to: ```js await INSERT.into(Books).entries([ @@ -741,7 +652,13 @@ SELECT.from('Foo') //> [{ foo:1 }, ...] SELECT('bar').from('Foo') //> ERROR: no columns to read ``` +### <> operator {.impl .node} +Operator `<>` works as specified in SQL standard, while `name != 'John'` translates to `name <> 'John' OR name is null`. + +::: warning +This is a breaking change to the previous implementation. Before, `<>` was translated to `name <> 'John' OR name is null` as well. +::: ### Miscellaneous {.impl .node} @@ -750,7 +667,6 @@ SELECT('bar').from('Foo') //> ERROR: no columns to read - Table aliases must not contain dots. - CQNs with an empty columns array now throws an error. - `*` is not a column reference, use `columns: ['*']` instead of `columns: [{ref:'*'}]`. -- Operator `<>` works as specified in SQL standard, `name != 'John'` translates to `name <> 'John' OR name is null`. - Column names in CSVs must map to physical column names: ```csvc @@ -798,4 +714,4 @@ Having said this, there can indeed be scenarios where SQLite might be used also ::: warning SQLite has only limited support for concurrent database access due to it's very coarse lock granularity. This makes it badly suited for applications with high concurrency. -::: +::: diff --git a/guides/databases.md b/guides/databases.md index ce01c8281..7adbb3b21 100644 --- a/guides/databases.md +++ b/guides/databases.md @@ -11,7 +11,7 @@ impl-variants: true # Using Databases -
+{{ $frontmatter.synopsis }} [[toc]] @@ -263,7 +263,7 @@ Use the properties [cds.dataSource.csv.*](../java/development/properties#cds-dat spring: config.activate.on-profile: test cds - dataSource.csv.paths: + dataSource.csv.paths: - test/data/** ``` @@ -346,7 +346,7 @@ db.queryForList("SELECT from sqlite_schema where name like ?", name);
-When you run your server with `cds watch` during development, an in-memory database is bootstrapped automatically, with SQL DDL statements generated based on your CDS models. +When you run your server with `cds watch` during development, an in-memory database is bootstrapped automatically, with SQL DDL statements generated based on your CDS models.
diff --git a/guides/domain-modeling.md b/guides/domain-modeling.md index 00120d446..1346aca4d 100644 --- a/guides/domain-modeling.md +++ b/guides/domain-modeling.md @@ -427,7 +427,7 @@ entity Users { ... projects : Composition of many Members on projects.user = $self; } entity Members { // link table - key project : Association to Project; + key project : Association to Projects; key user : Association to Users; } ``` @@ -512,7 +512,7 @@ aspect NamedAspect { someAdditionalField : String } extend Books with NamedAspect; ``` -We can also apply named aspects as **includes** in an inheritence-like syntax: +We can also apply named aspects as **includes** in an inheritance-like syntax: ```cds entity Books : NamedAspect { ... } @@ -596,7 +596,7 @@ By generating `.texts` entities and associations behind the scenes, CAP's **out- ### Authorization Model -CAP supports out-of-the-box authorization by annotating services and entites with `@requires` and `@restrict` annotations like that: +CAP supports out-of-the-box authorization by annotating services and entities with `@requires` and `@restrict` annotations like that: ```cds entity Books @(restrict: [ diff --git a/guides/i18n.md b/guides/i18n.md index 234c161a2..76d7476c8 100644 --- a/guides/i18n.md +++ b/guides/i18n.md @@ -1,6 +1,6 @@ --- index: 51 -layout: cookbook +# layout: cookbook synopsis: > Guides you through the steps to internationalize your application to provide localized versions with respect to both Localized Models as well as Localized Data. status: released @@ -9,7 +9,7 @@ uacp: Used as link target from Help Portal at https://help.sap.com/products/BTP/ # Localization, i18n -
+{{ $frontmatter.synopsis }} _'Localization'_ is a means to adapting your app to the languages of specific target markets. diff --git a/guides/localized-data.md b/guides/localized-data.md index 46c2c098e..6d764aa2e 100644 --- a/guides/localized-data.md +++ b/guides/localized-data.md @@ -1,6 +1,6 @@ --- index: 52 -layout: cookbook +# layout: cookbook synopsis: > This guide extends the localization/i18n of static content, such as labels or messages, to serve localized versions of actual application data. status: released @@ -10,7 +10,7 @@ uacp: Used as link target from Help Portal at https://help.sap.com/products/BTP/ # Localized Data -
+{{ $frontmatter.synopsis }} Localized data refers to the maintenance of different translations of textual data and automatically fetching the translations matching the users' preferred language, with per-row fallback to default languages, if the required translations aren’t available. Language codes are in ISO 639-1 format. @@ -203,7 +203,7 @@ The user's preferred locale is determined from request parameters, user settings The resulting [normalized locale](i18n#normalized-locales) is available programmatically, in your event handlers. -* Node.js: `req.user.locale` +* Node.js: `req.locale` * Java: `eventContext.getParameterInfo().getLocale()` ### Propagating `$user.locale` to Databases {#propagating-of-user-locale} diff --git a/guides/media-data.md b/guides/media-data.md index 56faaf4bc..26e7ede8a 100644 --- a/guides/media-data.md +++ b/guides/media-data.md @@ -3,13 +3,13 @@ index: 55 title: Media Data synopsis: > CAP provides out-of-the-box support for serving media and other binary data. -layout: cookbook +# layout: cookbook status: released --- # Serving Media Data -
+{{ $frontmatter.synopsis }} [[toc]] diff --git a/guides/messaging/event-mesh.md b/guides/messaging/event-mesh.md index de1d1bd5a..feab10c22 100644 --- a/guides/messaging/event-mesh.md +++ b/guides/messaging/event-mesh.md @@ -1,5 +1,5 @@ --- -# layout: cookbook +# # layout: cookbook shorty: SAP Event Mesh status: released --- diff --git a/guides/messaging/index.md b/guides/messaging/index.md index afd161255..1ff90fa75 100644 --- a/guides/messaging/index.md +++ b/guides/messaging/index.md @@ -12,7 +12,7 @@ status: released Java
--> -
+{{ $frontmatter.synopsis }} [[toc]] diff --git a/guides/providing-services.md b/guides/providing-services.md index 725cbd6aa..2b10a086b 100644 --- a/guides/providing-services.md +++ b/guides/providing-services.md @@ -13,7 +13,7 @@ uacp: Used as link target from Help Portal at https://help.sap.com/products/BTP/ # Providing Services -
+{{ $frontmatter.synopsis }} [[toc]] @@ -416,7 +416,7 @@ That would basically search for occurrences of `"Heights"` in all text fields of #### Using the `@cds.search` Annotation {#using-cds-search-annotation} -By default all elements of type `String` of an entity are searched. Yet, sometimes you may want to deviate from this default and specify a different set of searchable elements, or to extend the search to associated entities. Use the `@cds.search` annotation to do so. The general usage is: +By default search is limited to the elements of type `String` of an entity that aren't [calculated](../cds/cdl#calculated-elements) or [virtual](../cds/cdl#virtual-elements). Yet, sometimes you may want to deviate from this default and specify a different set of searchable elements, or to extend the search to associated entities. Use the `@cds.search` annotation to do so. The general usage is: ```cds @cds.search: { @@ -449,6 +449,10 @@ entity Books { ... } Searches all elements of type `String` excluding the element `isbn`, which leaves the `title` and `descr` elements to be searched. +::: tip +You can explicitly annotate calculated elements to make them searchable, even though they aren't searchable by default. The virtual elements won't be searchable even if they're explicitly annotated. +::: + #### Extend Search to *Associated* Entities ```cds @@ -688,7 +692,7 @@ service Sue { } ``` -In addition to server-side input validation as introduced above, this adds a corresponding `@FieldControl` annotation to the EDMX so that OData / Fiori clients would enforce a valid entry, thereby avoiding unneccessary request rountrips: +In addition to server-side input validation as introduced above, this adds a corresponding `@FieldControl` annotation to the EDMX so that OData / Fiori clients would enforce a valid entry, thereby avoiding unnecessary request roundtrips: ```xml @@ -756,16 +760,16 @@ Add `@assert.target` annotation to the service definition as previously mentione ```cds entity Books { - key ID : UUID; - title : String; - author : Association to Authors @assert.target; - } + key ID : UUID; + title : String; + author : Association to Authors @assert.target; +} - entity Authors { - key ID : UUID; - name : String; - books : Association to many Books on books.author = $self; - } +entity Authors { + key ID : UUID; + name : String; + books : Association to many Books on books.author = $self; +} ``` **HTTP Request** — *assume that an author with the ID `"796e274a-c3de-4584-9de2-3ffd7d42d646"` doesn't exist in the database* @@ -1207,7 +1211,8 @@ POST .../sue/Foo(2)/Sue.order {"x":1} // bound action await srv.send('stock',{id:2}) // bound actions/functions await srv.send('getStock','Foo',{id:2}) - await srv.send('order','Foo',{id:2,x:3}) + //for passing the params property, use this syntax + await srv.send({ event: 'order', entity: 'Foo', data: {x:3}, params: {id:2} }) ``` > Note: Always pass the target entity name as second argument for bound actions/functions. diff --git a/guides/querying.md b/guides/querying.md index 43a27e2ab..225f91b76 100644 --- a/guides/querying.md +++ b/guides/querying.md @@ -5,7 +5,7 @@ synopsis: > Learn about CAP's intrinsic querying capabilities, which allows clients to request the exact data they need, and are key enablers for serving requests automatically. -layout: cookbook +# layout: cookbook breadcrumbs: - Cookbook - Querying @@ -14,7 +14,7 @@ breadcrumbs: # Querying and View Building -
+{{ $frontmatter.synopsis }} [[toc]] diff --git a/guides/security/aspects.md b/guides/security/aspects.md index 02a7eef20..accbe79b8 100644 --- a/guides/security/aspects.md +++ b/guides/security/aspects.md @@ -9,7 +9,7 @@ impl-variants: true # Security Aspects -
+{{ $frontmatter.synopsis }} ## Secure Communications { #secure-communications } @@ -357,12 +357,34 @@ Attackers can send malicious input data in a regular request to make the server #### Common Attack Patterns { #common-injection-attacks } -- CAP's intrinsic data querying engine is immune with regards to [SQL injections](https://owasp.org/www-community/attacks/SQL_Injection). +- CAP's intrinsic data querying engine is immune with regards to [SQL injections](https://owasp.org/www-community/attacks/SQL_Injection) that are introduced by query parameter values that are derived from malicious user input. [CQL statements](../querying) are transformed into prepared statements that are executed in SQL databases such as SAP HANA. +Be aware that injections are still possible even via CQL when the query structure (e.g. target entity, columns etc.) is based on user input: + +
+ +```java +String entity = ; +String column = ; +validate(entity, column); // validate entity and column, e.g. compare with positive list +Select.from(entity).columns(b -> b.get(column)); +``` + +
+ +
+ +```js +const entity = +const column = +validate(entity, column) // validate entity and column, e.g. compare with positive list +SELECT.from(entity).columns(column) +``` + +
::: warning -Be careful in custom code when modifying or creating CQL queries. Refrain from building the query structure (target entity, columns) directly on basis of request input. -You're encouraged to pass user data in the provided methods such as CQL values or entity data. +Be careful with custom code when creating or modifying CQL queries. Additional input validation is needed when the query structure depends on the request's input: ::: - [Cross Site Scripting (XSS)](https://owasp.org/www-community/attacks/xss) is used by attackers to inject a malicious script, which is executed in the browser session of an unsuspecting user. diff --git a/guides/security/data-protection-privacy.md b/guides/security/data-protection-privacy.md index 779037cc2..04b2ca99e 100644 --- a/guides/security/data-protection-privacy.md +++ b/guides/security/data-protection-privacy.md @@ -8,7 +8,7 @@ uacp: Used as link target from SAP Help Portal at https://help.sap.com/products/ # Data Protection & Privacy -
+{{ $frontmatter.synopsis }} ## General Statement { #dpp-statement } diff --git a/guides/security/overview.md b/guides/security/overview.md index 8ff94d7ec..e4520bbd6 100644 --- a/guides/security/overview.md +++ b/guides/security/overview.md @@ -8,7 +8,7 @@ uacp: Used as link target from SAP Help Portal at https://help.sap.com/products/ # Platform Security -
+{{ $frontmatter.synopsis }} ## Platform Compliance { #platform-compliance } diff --git a/guides/temporal-data.md b/guides/temporal-data.md index 8cb21aab9..460141cf2 100644 --- a/guides/temporal-data.md +++ b/guides/temporal-data.md @@ -1,6 +1,6 @@ --- index: 53 -layout: cookbook +# layout: cookbook synopsis: > CAP provides out-of-the-box support for declaring and serving date-effective entities with application-controlled validity, in particular to serve as-of-now and time-travel queries. status: released @@ -10,7 +10,7 @@ uacp: Used as link target from Help Portal at https://help.sap.com/products/BTP/ # Temporal Data -
+{{ $frontmatter.synopsis }} Temporal data allows you to maintain information relating to past, present, and future application time. Built-in support for temporal data follows the general principle of CDS to capture intent with models while staying conceptual, concise, and comprehensive, and minimizing pollution by technical artifacts. diff --git a/guides/using-services.md b/guides/using-services.md index ae6684f9f..fb761eb00 100644 --- a/guides/using-services.md +++ b/guides/using-services.md @@ -4,7 +4,7 @@ synopsis: > Learn how to use uniform APIs to consume local or remote services. redirect_from: - guides/consuming-services -layout: cookbook +# layout: cookbook status: released impl-variants: true # uacp: Used as link target from Help Portal at @@ -290,7 +290,7 @@ The service is automatically mocked, as you can see in the log output on server /> successfully deployed to sqlite in-memory db [cds] - serving RiskService { at: '/service/risk', impl: './srv/risk-service.js' } -[cds] - mocking API_BUSINESS_PARTNER { at: '/api-business-partner' } +[cds] - mocking API_BUSINESS_PARTNER { at: '/api-business-partner' } // [!code focus] [cds] - launched in: 1.104s [cds] - server listening on { url: 'http://localhost:4004' } @@ -323,7 +323,7 @@ entity API_BUSINESS_PARTNER.A_BusinessPartner { ... to_BusinessPartnerAddress : - Association to many API_BUSINESS_PARTNER.A_BusinessPartnerAddress { }; + Association to many API_BUSINESS_PARTNER.A_BusinessPartnerAddress { }; // [!code focus] }; entity API_BUSINESS_PARTNER.A_BusinessPartnerAddress { @@ -1259,7 +1259,7 @@ cds: Run your application with the Destination service: -``` +```sh cds bind --exec -- mvn spring-boot:run \ -Dspring-boot.run.profiles=default,hybrid ``` diff --git a/java/advanced.md b/java/advanced.md index eb24b2790..1991e7ad5 100644 --- a/java/advanced.md +++ b/java/advanced.md @@ -16,7 +16,7 @@ uacp: Used as link target from Help Portal at https://help.sap.com/products/BTP/ } -
+{{ $frontmatter.synopsis }} diff --git a/java/application-services.md b/java/application-services.md index e2bc8902b..252c294ba 100644 --- a/java/application-services.md +++ b/java/application-services.md @@ -12,7 +12,7 @@ uacp: Used as link target from Help Portal at https://help.sap.com/products/BTP/ } -
+{{ $frontmatter.synopsis }} ## Handling CRUD Events { #crudevents} @@ -44,7 +44,7 @@ To learn more about the entity data argument `List books` of the event ha Application Services are used by OData protocol adapters to expose the Application Service's API as an OData API on a path with the following pattern: -``` +```txt http(s)://// ``` diff --git a/java/architecture.md b/java/architecture.md index e3fd8f4e3..b6842a292 100644 --- a/java/architecture.md +++ b/java/architecture.md @@ -6,7 +6,7 @@ status: released uacp: Used as link target from Help Portal at https://help.sap.com/products/BTP/65de2977205c403bbc107264b8eccf4b/9186ed9ab00842e1a31309ff1be38792.html --- -# Stack Architecture +# Modular Architecture -
+{{ $frontmatter.synopsis }} @@ -129,7 +129,7 @@ In addition, for activating the Spring integration of CAP Java SDK, the followin ``` -It might be more convenient to make use of CDS starter bundle `cds-starter-spring-boot-odata`, which not only comprises the necessary Spring dependencies, but also configures the OData V4 protocol adapter: +It might be easier to use the CDS starter bundle `cds-starter-spring-boot-odata`, which not only comprises the necessary Spring dependencies, but also configures the OData V4 protocol adapter: ```xml @@ -141,15 +141,15 @@ It might be more convenient to make use of CDS starter bundle `cds-starter-sprin ### Spring Features -Beside the common Spring features such as dependency injection and a sophisticated test framework, the following features are available in Spring CAP applications in addition: +Beside the common Spring features such as dependency injection and a sophisticated test framework, the following features are available in Spring CAP applications: * CDS event handlers within custom Spring beans are automatically registered at startup. * Full integration into Spring transaction management (`@Transactional` is supported). -* A various number of CAP Java SDK interfaces are exposed as [Spring beans](#exposed-beans) and are available in Spring application context such as technical services, the `CdsModel` or the `UserInfo` in current request scope. -* *Automatic* configuration of XSUAA, IAS and [mock user authentication](../security#mock-users) by means of Spring security configuration. +* A number of CAP Java SDK interfaces are exposed as [Spring beans](#exposed-beans) and are available in the Spring application context such as technical services, the `CdsModel`, or the `UserInfo` in current request scope. +* *Automatic* configuration of XSUAA, IAS, and [mock user authentication](../security#mock-users) by means of Spring security configuration. * Integration of `cds`-property section into Spring properties. See section [Externalized Configuration](https://docs.spring.io/spring-boot/docs/current/reference/html/features.html#features.external-config) in the Spring Boot documentation for more details. -* [cds actuator](../observability#spring-boot-actuators) exposing monitoring information about CDS runtime and security. -* [DB health check indicator](../observability#spring-health-checks) which also applies to tenant-aware DB connections. +* [The cds actuator](../observability#spring-boot-actuators) exposing monitoring information about CDS runtime and security. +* [The DB health check indicator](../observability#spring-health-checks) which also applies to tenant-aware DB connections. ::: tip None of the listed features will be available out of the box in case you choose to pack and deploy your web application as plain Java Servlet in a *war* file. @@ -162,7 +162,7 @@ None of the listed features will be available out of the box in case you choose | :---------------------------------------------------- | :----------------------------------------------------- | :----------------------------------------------------- | | [CdsRuntime](https://www.javadoc.io/doc/com.sap.cds/cds-services-api/latest/com/sap/cds/services/runtime/CdsRuntime.html) | Runtime instance (singleton) | `@Autowired`
`CdsRuntime runtime;` | [CdsRuntimeConfigurer](https://www.javadoc.io/doc/com.sap.cds/cds-services-api/latest/com/sap/cds/services/runtime/CdsRuntimeConfigurer.html) | Runtime configuration instance (singleton) | `@Autowired`
`CdsRuntimeConfigurer configurer;` -| [Service](https://www.javadoc.io/doc/com.sap.cds/cds-services-api/latest/com/sap/cds/services/Service.html) | All kinds of CDS services, application services and technical services | `@Autowired`
`@Qualifier(CatalogService_.CDS_NAME)`
`private ApplicationService cs;`

`@Autowired`
`private PersistenceService ps;` +| [Service](https://www.javadoc.io/doc/com.sap.cds/cds-services-api/latest/com/sap/cds/services/Service.html) | All kinds of CDS services, application services, and technical services | `@Autowired`
`@Qualifier(CatalogService_.CDS_NAME)`
`private ApplicationService cs;`

`@Autowired`
`private PersistenceService ps;` | [ServiceCatalog](https://www.javadoc.io/doc/com.sap.cds/cds-services-api/latest/com/sap/cds/services/ServiceCatalog.html) | The catalog of all available services | `@Autowired`
`ServiceCatalog catalog;` | [CdsModel](https://javadoc.io/doc/com.sap.cds/cds4j-api/latest/com/sap/cds/reflect/CdsModel.html) | The current model | `@Autowired`
`CdsModel model;` | [UserInfo](https://www.javadoc.io/doc/com.sap.cds/cds-services-api/latest/com/sap/cds/services/request/UserInfo.html) | Information about the authenticated user | `@Autowired`
`UserInfo userInfo;` @@ -170,27 +170,82 @@ None of the listed features will be available out of the box in case you choose | [ParameterInfo](https://www.javadoc.io/doc/com.sap.cds/cds-services-api/latest/com/sap/cds/services/request/ParameterInfo.html) | Information about request parameters | `@Autowired`
`ParameterInfo paramInfo;` | [Messages](https://www.javadoc.io/doc/com.sap.cds/cds-services-api/latest/com/sap/cds/services/messages/Messages.html) | Interface to write messages | `@Autowired`
`Messages messages;` | [FeatureTogglesInfo](https://www.javadoc.io/doc/com.sap.cds/cds-services-api/latest/com/sap/cds/services/request/FeatureTogglesInfo.html) | Information about feature toggles | `@Autowired`
`FeatureTogglesInfo ftsInfo;` -| [CdsDataStore](https://javadoc.io/doc/com.sap.cds/cds4j-api/latest/com/sap/cds/CdsDataStore.html) | Direct access to default data store | `@Autowired`
`CdsDataStore ds;` | +| [CdsDataStore](https://javadoc.io/doc/com.sap.cds/cds4j-api/latest/com/sap/cds/CdsDataStore.html) | Direct access to the default data store | `@Autowired`
`CdsDataStore ds;` | +### GraalVM Native Image Support (beta) -## Minimum Dependency Versions +Since Spring Boot 3 it's possible to compile Spring Boot applications to stand-alone native executables leveraging GraalVM Native Images. +Native Image applications have faster startup times and require less memory. CAP Java provides compatibility with the Native Image technology. -The CAP Java SDK uses various dependencies that are also used by the applications themselves. If the applications decide to manage versions of these dependencies it is helpful to know the minimum versions of these dependencies that the CAP Java SDK requires. The following table lists these minimum versions for various common dependencies, based on the latest release. +[Learn more about Native Image support in Spring Boot.](https://docs.spring.io/spring-boot/docs/current/reference/html/native-image.html){.learn-more} -### Maintenance Version 1.34.x +If you want to compile your application as a native executable the following boundary conditions need to be considered: -| Dependency | Minimum Version | Recommended Version | -| --- | --- | --- | -| Java | 8 | 17 | -| @sap/cds-dk | 4 | latest | -| @sap/cds-compiler | 2 | latest | -| Spring Boot1 | 2.7 | 2.7 | -| XSUAA | 2.13 | latest | -| SAP Cloud SDK | 4.10 | latest | +1. The GraalVM Native Image build analyzes your application from the `main` entry point. Only the code that is reachable through static analysis is included into the native image. This means that the full classpath needs to be known and available already at build time. + +2. Dynamic elements of your code, such as usage of reflection, JDK proxies, or resources need to be registered with the GraalVM Native Image build. You can learn more about this in the [GraalVM Native Image documentation](https://www.graalvm.org/latest/reference-manual/native-image/metadata/). + + ::: tip + Many runtime hints for reflection, JDK proxy usage, and resources are contributed automatically to the Native Image build. + This includes + - Required reflection for event handler classes defined in application code. + - JDK proxies for interfaces generated from the application's CDS model by the CDS Maven Plugin. + ::: + +3. Spring Boot automatically defines and fixes all bean definitions of your application at build time. If you have bean definitions that are created based on conditions on externalized configuration or profiles, you need to supply these triggers to the Native Image build. + + CAP Java also creates various bean definitions based on service bindings. Therefore, you need to provide the metadata of expected service bindings at runtime already during build time. This is similar to the information you define in deployment descriptors (for example `mta.yaml` or Helm charts). This information is also required to be supplied to the Native Image build. + + The Spring Boot Maven Plugin allows you to [configure the Spring profiles](https://docs.spring.io/spring-boot/docs/current/reference/html/howto.html#howto.aot.conditions) that are used during the Native Image build. You can supply information to the Native Image Build in a `native-build-env.json`, which you can configure together with the Spring profile. For example you can provide information to the Native image build in the `native-build-env.json` which you can configure together with the spring profile in the `srv/pom.xml`: + + ::: code-group + ```json [native-build-env.json] + { + "hana": [ { "name": "" } ], + "xsuaa": [ { "name": "" } ] + } + ``` + ```xml [srv/pom.xml] + + native + + + + + org.springframework.boot + spring-boot-maven-plugin + + + process-aot + + cloud + -Dcds.environment.local.defaultEnvPath=../native-build-env.json + + + + + + + + + ``` + ::: + +When using Spring Boot's parent POM, you can easily trigger the Native Image build by executing `mvn spring-boot:build-image -Pnative`. +This builds a Docker image using Cloud Native Buildpacks including a minimized OS and your application. +You can launch the Docker image by running `docker run --rm -p 8080:8080 :`. + +::: tip +If you want to try out CAP's Native Image support you can use the [SFlight sample application](https://github.com/SAP-samples/cap-sflight) which is prepared for GraalVM Native Images. +Note, that SFlight's native executable is built and configured to use SAP HANA and XSUAA by default. You therefore need to run it with the `cloud` profile and supply an SAP HANA and XSUAA service binding. +Alternatively you can make corresponding adaptations in `native-build-env.json` and `srv/pom.xml` to build the native executable for a different set of service bindings and profile. +::: + +## Minimum Dependency Versions -1 Spring Boot 3.0 is supported in `2.x` feature version only. +The CAP Java SDK uses various dependencies that are also used by the applications themselves. If the applications decide to manage the versions of these dependencies, it's helpful to know the minimum versions of these dependencies that the CAP Java SDK requires. The following table lists these minimum versions for various common dependencies, based on the latest release. -### Feature Version 2.x { #dependencies-version-2 } +### Active Version 2.x { #dependencies-version-2 } | Dependency | Minimum Version | Recommended Version | | --- | --- | --- | @@ -202,6 +257,24 @@ The CAP Java SDK uses various dependencies that are also used by the application | SAP Cloud SDK | 4.13 | latest | | Java Logging | 3.7 | latest | +::: warning +The Cloud SDK BOM `sdk-bom` manages XSUAA until version 2.x, which isn't compatible with CAP Java 2.x. +You have two options: +* Replace `sdk-bom` with `sdk-modules-bom`, which [manages all Cloud SDK dependencies but not the transitive dependencies.](https://sap.github.io/cloud-sdk/docs/java/guides/manage-dependencies#the-sap-cloud-sdk-bill-of-material) +* Or, add [dependency management for XSUAA](https://github.com/SAP/cloud-security-services-integration-library#installation) before Cloud SDK's `sdk-bom`. +::: + +### Maintenance Version 1.34.x (LTS) + +| Dependency | Minimum Version | Recommended Version | +| --- | --- | --- | +| Java | 8 | 17 | +| @sap/cds-dk | 4 | 6 | +| @sap/cds-compiler | 2 | 3 | +| Spring Boot | 2.7 | 2.7 | +| XSUAA | 2.13 | latest | +| SAP Cloud SDK | 4.10 | latest | + ## Building CAP Java Applications This section describes various options to create a CAP Java project from scratch, to build your application with Maven, and to modify an existing project with the CDS Maven plugin. @@ -226,7 +299,7 @@ mvn archetype:generate `-DarchetypeArtifactId=cds-services-archetype `-Darchetyp
-It supports the following command line options: +It supports the following command-line options: | Option | Description | | -- | -- | @@ -253,10 +326,10 @@ If you can't stick to defaults, you can use the _.cdsrc.json_ to add specific co [Learn more about configuration and `cds.env`](../../node.js/cds-env){.learn-more} -#### Using a specific cds-dk version +#### Using a Specific cds-dk Version By default, the build is configured to download a Node.js runtime and the `@sap/cds-dk` tools and install them locally within the project. -The `install-cdsdk` goal requires a version of `@sap/cds-dk` which [needs to be provided explicitly](../../releases/archive/2022/oct22#important-changes-in-java) in the configuration. With this you can ensure that the build is fully reproducible. +The `install-cdsdk` goal requires a version of `@sap/cds-dk`, which [needs to be provided explicitly](../../releases/archive/2022/oct22#important-changes-in-java) in the configuration. With this, you can ensure that the build is fully reproducible. You can provide this version by adding the following property to the `properties` section in your `pom.xml`: ```xml @@ -300,8 +373,8 @@ By default, the goal `install-cdsdk` of the `cds-maven-plugin` skips the install This should be done at least with every **major update** of `@sap/cds-dk`. ::: -### Increased developer efficiency with Spring Boot Devtools -In order to speed up your development turnaround you can add the [Spring Boot Devtools](https://docs.spring.io/spring-boot/docs/current/reference/html/using.html#using.devtools) dependency to your CAP Java application. Just add this dependency to the `pom.xml` of your `srv` module: +### Increased Developer Efficiency with Spring Boot Devtools +You can speed up your development turnaround by adding the [Spring Boot Devtools](https://docs.spring.io/spring-boot/docs/current/reference/html/using.html#using.devtools) dependency to your CAP Java application. Just add this dependency to the `pom.xml` of your `srv` module: ```xml @@ -310,20 +383,20 @@ In order to speed up your development turnaround you can add the [Spring Boot De ``` -Once this is added, you can use the restart capabilities of the Spring Boot Devtools while developing your application in your favorite Java IDE. Any change triggers an automatic application context reload without the need to manually restart the complete application. Besides being a lot faster than the complete restart this also eliminates manual steps. The application context reload is triggered by any file change on the application's classpath: +Once this is added, you can use the restart capabilities of the Spring Boot Devtools while developing your application in your favorite Java IDE. Any change triggers an automatic application context reload without the need to manually restart the complete application. Besides being a lot faster than a complete restart this also eliminates manual steps. The application context reload is triggered by any file change on the application's classpath: * Java classes (e.g. custom handlers) -* Anything below src/main/resources +* Anything inside src/main/resources * Configuration files (e.g. application.yaml) * Artifacts generated from CDS (schema.sql, CSN, EDMX) * Any other static resource -#### Spring Boot Devtools and CDS build +#### Spring Boot Devtools and CDS Build -The Spring Boot Devtools have no knowledge of any CDS tooling or the CAP Java runtime. Thus, they can't trigger a CDS build in case of changed CDS sources. For more information, please check the [Local Development Support](#local-development-support) section. +The Spring Boot Devtools have no knowledge of any CDS tooling or the CAP Java runtime. Thus, they can't trigger a CDS build if there are changes in the CDS source files. For more information, please check the [Local Development Support](#local-development-support) section. ::: tip -Especially CDS builds result in a lot of changed resources in your project. To have a smooth experience, define a [trigger file](https://docs.spring.io/spring-boot/docs/current/reference/html/using.html#using.devtools.restart.triggerfile) and [use `auto-build` goal](#cds-auto-build) of the CDS Maven plugin started from the command line. +CDS builds in particular change numerous resources in your project. To have a smooth experience, define a [trigger file](https://docs.spring.io/spring-boot/docs/current/reference/html/using.html#using.devtools.restart.triggerfile) and [use `auto-build` goal](#cds-auto-build) of the CDS Maven plugin started from the command line. ::: ### CDS Maven Plugin { #cds-maven-plugin} @@ -331,12 +404,12 @@ Especially CDS builds result in a lot of changed resources in your project. To h CDS Maven plugin provides several goals to perform CDS-related build steps. It can be used in CAP Java projects to perform the following build tasks: - Install Node.js in the specified version -- Install the CDS Development Kit `@sap/cds-dk` in a specified version +- Install the CDS Development Kit `@sap/cds-dk` with a specified version - Perform arbitrary CDS commands on a CAP Java project - Generate Java classes for type-safe access - Clean a CAP Java project from artifacts of the previous build -Since CAP Java 1.7.0, that CDS Maven Archetype sets up projects to leverage the CDS Maven plugin to perform the previous mentioned build tasks. On how to modify a project generated with a previous version of the CDS Maven Archetype, see [this commit](https://github.com/SAP-samples/cloud-cap-samples-java/commit/ceb47b52b1e30c9a3f6e0ea29e207a3dad3c0190). +Since CAP Java 1.7.0, that CDS Maven Archetype sets up projects to leverage the CDS Maven plugin to perform the previous mentioned build tasks. To have an example on how you can modify a project generated with a previous version of the CDS Maven Archetype, see [this commit](https://github.com/SAP-samples/cloud-cap-samples-java/commit/ceb47b52b1e30c9a3f6e0ea29e207a3dad3c0190). See [CDS Maven Plugin documentation](../assets/cds-maven-plugin-site/plugin-info.html){target="_blank"} for more details. @@ -356,10 +429,10 @@ cd srv mvn cds:watch ``` -It builds and starts the application and looks for changes in the CDS model. If you make changes to the CDS model, these are recognized and a restart of the application is initiated to make the changes effective. +It builds and starts the application and looks for changes in the CDS model. If you change the CDS model, these are recognized and a restart of the application is initiated to make the changes effective. The `watch` goal uses the `spring-boot-maven-plugin` internally to start the application with the goal `run` (this also includes a CDS build). Therefore, it's required that the application is a Spring Boot application and that you execute the `watch` goal within your service module folder. -When you add the [Spring Boot Devtools](https://docs.spring.io/spring-boot/docs/current/reference/html/using.html#using.devtools) to your project, the `watch` goal can take advantage of the reload mechanism described in the linked section. In case your application does not use the Spring Boot Devtools the `watch` goal performs a complete restart of the Spring Boot application after CDS model changes. As the application context reload is always faster than the complete restart the approach using the Spring Boot Devtools is the preferred approach. +When you add the [Spring Boot Devtools](https://docs.spring.io/spring-boot/docs/current/reference/html/using.html#using.devtools) to your project, the `watch` goal can take advantage of the reload mechanism. In case your application doesn't use the Spring Boot Devtools the `watch` goal performs a complete restart of the Spring Boot application after CDS model changes. As the application context reload is always faster than a complete restart the approach using the Spring Boot Devtools is the preferred approach. ::: warning The `watch` goal only works on Windows if the Spring Boot Devtools are enabled. @@ -373,7 +446,11 @@ If you want to have the comfort of an automated CDS build like with the `watch` If the Spring Boot Devtools configuration of your CAP Java application defines a [trigger file](https://docs.spring.io/spring-boot/docs/current/reference/html/using.html#using.devtools.restart.triggerfile), the `auto-build` can detect this and touch the trigger file in case of any file change. The same applies to the `watch` goal. ::: -
+#### Local Development for Multitenant Applications + +With the streamlined MTX, you can run your multitenant application locally along with the MTX sidecar and use SQLite as the database. See [the _Deploy as SaaS_ guide](../../guides/deployment/as-saas?impl-variant=java#local-mtx) for more information. + +
## Testing CAP Java Applications @@ -384,7 +461,7 @@ As described in [Modular Architecture](../architecture#modular_architecture), a Typical areas that require testing are the [services](../consumption-api#cdsservices) that dispatch events to [event handlers](../provisioning-api), the event handlers themselves that implement the behaviour of the services, and finally the APIs that the application services define and that are exposed to clients through [OData](../application-services#odata-requests). ::: tip -Aside from [JUnit](https://junit.org/junit5/), the [Spring framework](https://docs.spring.io/spring-framework/docs/current/reference/html/index.html) provides much convenience for both unit and integration testing, like dependency injection via [*autowiring*](https://docs.spring.io/spring-framework/docs/current/reference/html/core.html#beans-factory-autowire) or the usage of [MockMvc](https://docs.spring.io/spring-framework/docs/current/reference/html/testing.html#spring-mvc-test-framework) and [*mocked users*]( https://docs.spring.io/spring-security/reference/servlet/test/method.html#test-method-withmockuser). So whenever possible, it is recommended to utilize it for writing tests. +Aside from [JUnit](https://junit.org/junit5/), the [Spring framework](https://docs.spring.io/spring-framework/docs/current/reference/html/index.html) provides much convenience for both unit and integration testing, like dependency injection via [*autowiring*](https://docs.spring.io/spring-framework/docs/current/reference/html/core.html#beans-factory-autowire) or the usage of [MockMvc](https://docs.spring.io/spring-framework/docs/current/reference/html/testing.html#spring-mvc-test-framework) and [*mocked users*]( https://docs.spring.io/spring-security/reference/servlet/test/method.html#test-method-withmockuser). So whenever possible, it's recommended to use it for writing tests. ::: ### Best Practices @@ -460,7 +537,7 @@ Whereas `discountBooks` is registered to the `After` phase of a `read` event on #### Event Handler Layer Testing -Out of these two handler methods `discountBooks` does not actually depend on the `PersistenceService`. +Out of these two handler methods `discountBooks` doesn't actually depend on the `PersistenceService`. That allows us to verify its behavior in a unit test by creating a `CatalogServiceHandler` instance with the help of a `PersistenceService` mock to invoke the handler method on, as demonstrated below: @@ -553,7 +630,7 @@ public class CatalogServiceTest { } ``` -The same way you can verify the `ServiceException` being thrown in case of the order quantity exceeding the stock value: +In the same way you can verify that the `ServiceException` is being thrown when the order quantity exceeds the stock value: ```java @SpringBootTest diff --git a/java/getting-started.md b/java/getting-started.md index 24fb4650f..98e5f7298 100644 --- a/java/getting-started.md +++ b/java/getting-started.md @@ -15,7 +15,7 @@ status: released } -
+{{ $frontmatter.synopsis }} ## Introduction @@ -60,6 +60,7 @@ This section describes the prerequisites and tools to build a CAP application lo ``` ::: tip For a preconfigured environment, use [SAP Business Application Studio](../tools/#bastudio), which comes with all of the required tools preinstalled. +In older workspaces it might be necessary to explicitly set the JDK to version 17 with the command `Java: Set Default JDK`. ::: ## Starting a New Project { #new-project} @@ -121,7 +122,7 @@ CAP Java also provides a starter bundle for SAP BTP Kyma environment. See [CAP S The generated project has the following folder structure: -``` +```txt / |-- db/ `-- data-model.cds @@ -171,7 +172,7 @@ mvn com.sap.cds:cds-maven-plugin:addIntegrationTest ``` This command also creates a new folder *integration-tests/src/test/java*, which contains integration test classes: -``` +```txt / `-- integration-tests/ `-- src/test/java/ diff --git a/java/indicating-errors.md b/java/indicating-errors.md index 22265c2a9..f02d71a17 100644 --- a/java/indicating-errors.md +++ b/java/indicating-errors.md @@ -12,7 +12,7 @@ uacp: Used as link target from Help Portal at https://help.sap.com/products/BTP/ } -
+{{ $frontmatter.synopsis }} ## Overview @@ -102,17 +102,18 @@ You can localize these strings, by putting them into property files and passing When running your application on Spring, the CAP Java SDK integrates with [Spring's support for handling text resource bundles](https://docs.spring.io/spring-boot/docs/current/reference/html/features.html#features.internationalization). This handling by default expects translated texts in a `messages.properties` file under `src/main/resources`. The texts defined in the resource bundles can be formatted based on the syntax defined by `java.text.MessageFormat`. -When the message or exception text is sent to the client it’s localized using the client's locale, as described [here](../guides/i18n#user-locale). +When the message or exception text is sent to the client it’s localized using the client's locale, as described [in the Localization Cookbook](../guides/i18n#user-locale). -`messages.properties` -``` +::: code-group +```properties [messages.properties] my.message.key = This is a localized message with {0} parameters ``` -`messages_de.properties` -``` +```properties [messages_de.properties] my.message.key = Das ist ein übersetzter Text mit {0} Parametern ``` +::: + ```java // localized message with placeholders diff --git a/java/messaging-foundation.md b/java/messaging-foundation.md index 4efd6eb17..593b1233d 100644 --- a/java/messaging-foundation.md +++ b/java/messaging-foundation.md @@ -27,7 +27,7 @@ status: released } -
+{{ $frontmatter.synopsis }} In contrast, the nature of synchronous communication between services can be disadvantageous depending on the desired information flow, for example, sender and receiver need to be available at the time of the request. The sender needs to know the receiver and how to call it, and that communication per request is usually point-to-point only. @@ -781,7 +781,7 @@ hello world If the service is configured with the structured flag, the message is converted to a map and on the the consumer side `TopicMessageEventContext.getData()` returns: ```json -{"message": "hello world"} +{"data": {"message": "hello world"}} ``` #### Handling events @@ -860,4 +860,3 @@ When using a CAP messaging service directly to emit the raw message payload as a [Learn more about **CloudEvents**.](../guides/messaging/#cloudevents){.learn-more} - diff --git a/java/migration.md b/java/migration.md index 1acc70b49..342123472 100644 --- a/java/migration.md +++ b/java/migration.md @@ -20,7 +20,7 @@ uacp: Used as link target from Help Portal at https://help.sap.com/products/BTP/ } -
+{{ $frontmatter.synopsis }} [[toc]] @@ -70,6 +70,13 @@ CAP Java 2.0 itself requires updated [dependency versions](./development/#depend - SAP Cloud SDK - Java Logging (replace `cf-java-logging-support-servlet` with `cf-java-logging-support-servlet-jakarta`) +::: warning +The Cloud SDK BOM `sdk-bom` manages XSUAA until version 2.x, which isn't compatible with CAP Java 2.x. +You have two options: +* Replace `sdk-bom` with `sdk-modules-bom`, which [manages all Cloud SDK dependencies but not the transitive dependencies.](https://sap.github.io/cloud-sdk/docs/java/guides/manage-dependencies#the-sap-cloud-sdk-bill-of-material) +* Or, add [dependency management for XSUAA](https://github.com/SAP/cloud-security-services-integration-library#installation) before Cloud SDK's `sdk-bom`. +::: + ### API Cleanup Some interfaces, methods, configuration properties and annotations, which had already been deprecated in 1.x, are now removed in version 2.0. Please strictly fix all usage of [deprecated APIs](#overview-of-removed-interfaces-and-methods) by using the recommended replacement. @@ -678,7 +685,7 @@ Also replace the classic handler return types with the corresponding new impleme There are numerous files in your classic project, which aren’t required and supported anymore in the new project. Don't copy any of the following files to the new project: -``` +```txt / |-- db/ | |-- .build.js diff --git a/java/observability.md b/java/observability.md index 20beed351..8e3a6f89d 100644 --- a/java/observability.md +++ b/java/observability.md @@ -12,7 +12,7 @@ status: released } -
+{{ $frontmatter.synopsis }} @@ -235,7 +235,7 @@ You can add a Dynatrace connection to your CAP Java application by [additional c ### Tracing { #tracing} -To minimize overhead at runtime, monitoring information is gathered rather on a global application level and hence might not be sufficient to troubleshoot specific issues. In such a situation, the use of more focused tracing tools can be an option. Typically, such tools are capable of focusing a specific aspect of an application (for instance JVM Garbage Collection), but they come with an additional overhead and therefore shouln't be constantly active. Hence, they need to meet following requirements: +To minimize overhead at runtime, monitoring information is gathered rather on a global application level and hence might not be sufficient to troubleshoot specific issues. In such a situation, the use of more focused tracing tools can be an option. Typically, such tools are capable of focusing a specific aspect of an application (for instance JVM Garbage Collection), but they come with an additional overhead and therefore shouldn't be constantly active. Hence, they need to meet following requirements: * Switchable at runtime * Use a communication channel not exposed to unauthorized users diff --git a/java/outbox.md b/java/outbox.md index c0e5aac99..5e8283d65 100644 --- a/java/outbox.md +++ b/java/outbox.md @@ -11,7 +11,7 @@ status: released } -
+{{ $frontmatter.synopsis }} @@ -91,4 +91,3 @@ Persistent outbox is supported starting with these version: `@sap/cds: 5.7.0`, To manually delete entries in the `cds.outbox.Messages` table, you can either expose it in a service or programmatically modify it using the `cds.outbox.Messages` database entity. - diff --git a/java/persistence-services.md b/java/persistence-services.md index b636fdb99..bc33f06e0 100644 --- a/java/persistence-services.md +++ b/java/persistence-services.md @@ -13,7 +13,7 @@ uacp: Used as link target from Help Portal at https://help.sap.com/products/BTP/ } -
+{{ $frontmatter.synopsis }} ## Database Support { #database-support} @@ -31,23 +31,21 @@ SAP HANA is supported as the CAP standard database and recommended for productiv CAP Java SDK is tested on [PostgreSQL](https://www.postgresql.org/) 15 and supports most of the CAP features. Known limitations are: -1. CAP can create an _initial_ database schema only. There is no automatic schema evolution. -2. No locale specific sorting. The sort order of queries behaves as configured on the database. -3. Write operations through CDS views are only supported for views that can be [resolved](query-execution#updatable-views) or are [updatable](https://www.postgresql.org/docs/14/sql-createview.html#SQL-CREATEVIEW-UPDATABLE-VIEWS) in PostgreSQL. -4. The CDS type `UInt8` can't be used with PostgreSQL, as there is no `TINYINT`. Use `Int16` instead. +1. No locale specific sorting. The sort order of queries behaves as configured on the database. +2. Write operations through CDS views are only supported for views that can be [resolved](query-execution#updatable-views) or are [updatable](https://www.postgresql.org/docs/14/sql-createview.html#SQL-CREATEVIEW-UPDATABLE-VIEWS) in PostgreSQL. +3. The CDS type `UInt8` can't be used with PostgreSQL, as there's no `TINYINT`. Use `Int16` instead. +4. [Multitenancy](../guides/multitenancy/) and [extensibility](../guides/extensibility/) aren't yet supported on PostgreSQL. ### H2 Database [H2](https://www.h2database.com/html/main.html) is one of the recommended in-memory databases for local development. There’s no production support for H2 from CAP and there are the following support limitations: -1. [Localization](../guides/localized-data#behind-the-scenes) is supported for the locales configured in [cds.sql.supportedLocales](../java/development/properties#cds-sql-supportedLocales). Ensure that the corresponding localized views are generated by setting the `i18n.for_sql` property as shown in the [Localized Data guide](../guides/localized-data#resolving-localized-texts-via-views). -2. H2 only supports database level collation. Lexicographical sorting on character-based columns isn’t supported. -3. Case-insensitive comparison isn’t yet supported. -4. Currently, only [as-of-now queries](../guides/temporal-data#as-of-now-queries) with temporal data are supported. -5. By default, views aren’t updatable on H2. However, the CAP Java SDK supports some views to be updatable as described [here](query-execution#updatable-views). -6. Although referential and foreign key constraints are supported, H2 [doesn't support deferred checking](http://www.h2database.com/html/grammar.html#referential_action). As a consequence, schema SQL is never generated with referential constraints. -7. In [pessimistic locking](query-execution#pessimistic-locking), _shared_ locks are not supported but an _exclusive_ lock is used instead. -8. The CDS type `UInt8` can't be used with H2, as there is no `TINYINT`. Use `Int16` instead. +1. H2 only supports database level collation. Lexicographical sorting on character-based columns isn’t supported. +2. Case-insensitive comparison isn’t yet supported. +3. By default, views aren’t updatable on H2. However, the CAP Java SDK supports some views to be updatable as described [here](query-execution#updatable-views). +4. Although referential and foreign key constraints are supported, H2 [doesn't support deferred checking](http://www.h2database.com/html/grammar.html#referential_action). As a consequence, schema SQL is never generated with referential constraints. +5. In [pessimistic locking](query-execution#pessimistic-locking), _shared_ locks are not supported but an _exclusive_ lock is used instead. +6. The CDS type `UInt8` can't be used with H2, as there is no `TINYINT`. Use `Int16` instead. ### SQLite @@ -104,9 +102,9 @@ SAP HANA can be configured when running locally as well as when running producti Service bindings of type *service-manager* and, in a Spring-based application, *hana* are used to auto-configure datasources. If multiple datasources are used by the application, you can select one auto-configured datasource to be used by the default Persistence Service through the property `cds.dataSource.binding`. -### PostgreSQL +### PostgreSQL { #postgresql-1 } -CAP Java provides limited support for [PostgreSQL](https://www.postgresql.org/). The major limitation is that CAP can only create an _initial_ database schema but there is no automatic schema evolution. +PostgreSQL can be configured when running locally as well as when running productively in the cloud. Similar to HANA, the datasource is auto-configured based on available service bindings, if the feature `cds-feature-postgresql` is added. #### Initial Database Schema @@ -127,13 +125,14 @@ To generate a `schema.sql` for PostgreSQL, use the dialect `postgres` with the ` ``` The generated `schema.sql` can be automatically deployed by Spring if you configure the [sql.init.mode](https://docs.spring.io/spring-boot/docs/2.7.x/reference/html/howto.html#howto.data-initialization.using-basic-sql-scripts) to `always`. + ::: warning -Automatic schema deployment is not suitable for productive use. Consider using production-ready tools like Flyway or Liquibase. +Automatic schema deployment isn't suitable for productive use. Consider using production-ready tools like Flyway or Liquibase. See more on that in the [Database guide for PostgreSQL](../guides/databases-postgres.md?impl-variant=java#deployment-using-liquibase) ::: -#### Configure the PostgreSQL Database +#### Configure the Connection Data Explicitly { #postgres-connection } -Configure the connection data of your PostgreSQL database in the _application.yaml_: +If you don't have a compatible PostgreSQL service binding in your application environment, you can also explicitly configure the connection data of your PostgreSQL database in the _application.yaml_: ```yaml --- @@ -143,7 +142,7 @@ spring: url: username: password: - driver-class-name: org.postgres.Driver + driver-class-name: org.postgresql.Driver ``` ### H2 @@ -168,6 +167,11 @@ To generate a `schema.sql` for H2, use the dialect `h2` with the `cds deploy` co In Spring, H2 is automatically initialized in-memory when present on the classpath. See the official [documentation](http://www.h2database.com/html/features.html) for H2 for file-based database configuration. +The `cds-maven-plugin` provides the goal `add` that can be used to add H2 support to the CAP Java project: +```sh +mvn com.sap.cds:cds-maven-plugin:add -Dfeature=H2 -Dprofile=default +``` + ### SQLite #### Initial Database Schema @@ -196,6 +200,11 @@ Also, you need to enable compiler support for session context variables in _.cds In addition, in the _application.yaml_ file, set `cds.sql.supportedLocales: "*"` to advise the runtime to use session context variables. +The `cds-maven-plugin` provides the goal `add` that can be used to add Sqlite support to the CAP Java project: +```sh +mvn com.sap.cds:cds-maven-plugin:add -Dfeature=SQLITE -Dprofile=default +``` + #### File-Based Storage The database content is stored in a file, `sqlite.db` as in the following example. Since the schema is initialized using `cds deploy` command, the initialization mode is set to `never`: @@ -212,9 +221,6 @@ spring: driver-class-name: org.sqlite.JDBC hikari: maximum-pool-size: 1 -cds: - sql: - supportedLocales: "*" ``` #### In-Memory Storage @@ -229,16 +235,12 @@ spring: sql: init: mode: always - supportedLocales: "*" datasource: url: "jdbc:sqlite:file::memory:?cache=shared" driver-class-name: org.sqlite.JDBC hikari: maximum-pool-size: 1 max-lifetime: 0 -cds: - sql: - supportedLocales: "*" ``` ## Persistence Services @@ -467,4 +469,3 @@ cds: tenant-independent: dataSource: "tenantIndependentDataSource" ``` - diff --git a/java/provisioning-api.md b/java/provisioning-api.md index 6b63bc424..0b3d93946 100644 --- a/java/provisioning-api.md +++ b/java/provisioning-api.md @@ -1,6 +1,6 @@ --- synopsis: > - This section describes how to register event handlers on services. In CAP everything that happens at runtime is an event that is sent to a service. + This section describes how to register event handlers on services. In CAP everything that happens at runtime is an event that is sent to a service. With event handlers the processing of these events can be extended or overridden. Event handlers can be used to handle CRUD events, implement actions and functions and to handle asynchronous events from a messaging service. redirect_from: java/srv-impl status: released @@ -14,7 +14,8 @@ uacp: Used as link target from SAP Help Portal at https://help.sap.com/products/ } -
+This section describes how to register event handlers on services. In CAP everything that happens at runtime is an [event](../about/#events) that is sent to a [service](../about/#services). +With event handlers the processing of these events can be extended or overridden. Event handlers can be used to handle CRUD events, implement actions and functions and to handle asynchronous events from a messaging service. ## Introduction to Event Handlers diff --git a/java/query-api.md b/java/query-api.md index c44d1e850..cf4cd4ed6 100644 --- a/java/query-api.md +++ b/java/query-api.md @@ -1,6 +1,6 @@ --- synopsis: > - API to fluently build CQL statements in Java. + API to fluently build CQL statements in Java. redirect_from: java/cds-ql status: released uacp: Used as link target from Help Portal at https://help.sap.com/products/BTP/65de2977205c403bbc107264b8eccf4b/9186ed9ab00842e1a31309ff1be38792.html @@ -14,7 +14,7 @@ uacp: Used as link target from Help Portal at https://help.sap.com/products/BTP/ } -
+API to fluently build [CQL](../cds/cql) statements in Java. ## Introduction @@ -689,6 +689,20 @@ Select.from("bookshop.Books") .orderBy(c -> c.get("ID").desc(), c -> c.get("title").asc()); ``` +You can order by the alias of a column of the select list or a column that is defined as a result of the function call. + +```java +Select.from("bookshop.Person") + .columns(p -> p.get("name").toUpper().as("aliasForName")) + .orderBy(p -> p.get("aliasForName").asc()); +``` + +Aliases of columns have precedence over the element names when `orderBy` is evaluated. + +::: warning +Aliases may shadow elements names. To avoid shadowing, don't use element names as aliases. +:::: + On SAP HANA, the user's locale is passed to the database, resulting in locale-specific sorting of string-based columns. By default, `null` values come before non-`null` values when sorting in ascending order and after non-`null` values when sorting in descending order. Use the `ascNullsLast` and `descNullsFirst` methods if you need to change this behavior. diff --git a/java/query-execution.md b/java/query-execution.md index 81be40d06..f1f664dfd 100644 --- a/java/query-execution.md +++ b/java/query-execution.md @@ -1,5 +1,5 @@ --- -synopsis: API to execute CQL statements on services accepting CQN queries. +synopsis: API to execute CQL statements on services accepting CQN queries. status: released uacp: Used as link target from Help Portal at https://help.sap.com/products/BTP/65de2977205c403bbc107264b8eccf4b/9186ed9ab00842e1a31309ff1be38792.html --- @@ -12,7 +12,7 @@ uacp: Used as link target from Help Portal at https://help.sap.com/products/BTP/ } -
+{{ $frontmatter.synopsis }} ## Query Execution { #queries} diff --git a/java/query-introspection.md b/java/query-introspection.md index b7766dac2..508a8b59b 100644 --- a/java/query-introspection.md +++ b/java/query-introspection.md @@ -1,6 +1,6 @@ --- synopsis: > - API to introspect CDS Query Language (CQL) statements in Java. + API to introspect CDS Query Language (CQL) statements in Java. status: released uacp: Used as link target from Help Portal at https://help.sap.com/products/BTP/65de2977205c403bbc107264b8eccf4b/9186ed9ab00842e1a31309ff1be38792.html --- @@ -12,7 +12,7 @@ uacp: Used as link target from Help Portal at https://help.sap.com/products/BTP/ } -
+{{ $frontmatter.synopsis }} ## Introduction @@ -136,7 +136,7 @@ CdsEntity item = result.targetEntity(); // OrderItems ### Extracting Filter Values -A non-complex filter predicate might map (restrict) some element to a particular _filter value_. If some filter values can be _unambiguously_ determined, the `CqnAnalyzer` can extract these filter values and return them as a `Map`. A filterd data set will contain only data that matches the filter values. +A non-complex filter predicate might map (restrict) some element to a particular _filter value_. If some filter values can be _unambiguously_ determined, the `CqnAnalyzer` can extract these filter values and return them as a `Map`. A filtered data set will contain only data that matches the filter values. Examples: @@ -370,7 +370,7 @@ for (Map book : books) { The output will be: -``` +```txt Catweazle no match The Raven match Dracula no match diff --git a/java/reflection-api.md b/java/reflection-api.md index 3e8ae9105..25ab6587c 100644 --- a/java/reflection-api.md +++ b/java/reflection-api.md @@ -14,7 +14,7 @@ uacp: Used as link target from Help Portal at https://help.sap.com/products/BTP/ } -
+{{ $frontmatter.synopsis }} ## The CDS Model @@ -192,7 +192,7 @@ CAP Java does not make any assumption _how_ the set of enabled features (_active Features are modeled in CDS by dividing up CDS code concerning separate features into separate subfolders of a common `fts` folder of your project, as shown by the following example: -``` +```txt |-- [db] | |-- my-model.cds | `-- ... diff --git a/java/remote-services.md b/java/remote-services.md index dfbd78478..601d2cff4 100644 --- a/java/remote-services.md +++ b/java/remote-services.md @@ -13,7 +13,7 @@ uacp: Used as link target from Help Portal at https://help.sap.com/products/BTP/ } -
+{{ $frontmatter.synopsis }} ## Enabling Remote Services @@ -131,7 +131,7 @@ When loading destinations from SAP BTP Destination Service, you can specify a [d These strategies can be set in the destination configuration of the _Remote Service_: -``` +```yml cds: remote.services: - name: "API_BUSINESS_PARTNER" diff --git a/java/request-contexts.md b/java/request-contexts.md index 827293256..8abad9579 100644 --- a/java/request-contexts.md +++ b/java/request-contexts.md @@ -12,7 +12,7 @@ uacp: Used as link target from Help Portal at https://help.sap.com/products/BTP/ } -
+{{ $frontmatter.synopsis }} ## Overview diff --git a/java/security.md b/java/security.md index cd8f61295..aa863a997 100644 --- a/java/security.md +++ b/java/security.md @@ -23,7 +23,7 @@ uacp: Used as link target from Help Portal at https://help.sap.com/products/BTP/ } -
+{{ $frontmatter.synopsis }} { #security} @@ -63,7 +63,7 @@ Recommended alternative is to use the `cds-starter-cloudfoundry` or the `cds-sta Your application is secured by IAS-authentication **automatically**, if 1. Following dependencies are set: - * `spring-security-starter` that brings Spring Security and [Java security library](https://github.com/SAP/cloud-security-xsuaa-integration) + * `resourceserver-security-spring-boot-starter` that brings Spring Security and [Java security library](https://github.com/SAP/cloud-security-xsuaa-integration) * `cds-feature-identity` 2. The application is bound to an [IAS service instance](https://help.sap.com/docs/IDENTITY_AUTHENTICATION) ::: warning @@ -82,7 +82,7 @@ Your application is secured by the hybrid mode **automatically**, if 2. The application is additionally bound to an [XSUAA service instance](../guides/authorization#xsuaa-configuration) ::: tip -In hybrid mode, the same constraints in regards to multiple XSUAA bindings applies as descibed in [Configure XSUAA Authentication](#xsuaa) +In hybrid mode, the same constraints in regards to multiple XSUAA bindings applies as described in [Configure XSUAA Authentication](#xsuaa) ::: ### Automatic Spring Boot Security Configuration { #spring-boot} @@ -164,9 +164,9 @@ public class AppSecurityConfig { @Bean public SecurityFilterChain appFilterChain(HttpSecurity http) throws Exception { return http - .requestMatchers().antMatchers("/public/**").and() - .csrf().disable() // don't insist on csrf tokens in put, post etc. - .authorizeRequests().anyRequest().permitAll().and() + .securityMatcher(AntPathRequestMatcher.antMatcher("/public/**")) + .csrf(c -> c.disable()) // don't insist on csrf tokens in put, post etc. + .authorizeHttpRequests(r -> r.anyRequest().permitAll()) .build(); } @@ -181,12 +181,6 @@ The Spring `SecurityFilterChain` requires CAP Java SDK [1.27.x](../releases/arch Be cautious with the configuration of the `HttpSecurity` instance in your custom configuration. Make sure that only the intended endpoints are affected. ::: -Example: -```java -http.authorizeRequests().antMatchers("/public/**").permitAll() -``` -Opens *all* endpoints of the application, which is hardly intended. - Another typical example is the configuration of [Spring Actuators](https://docs.spring.io/spring-boot/docs/current/reference/html/actuator.html#actuator.enabling). For example a custom configuration can apply basic authentication to actuator endpoints `/actuator/**`: ```java @@ -198,10 +192,10 @@ public class ActuatorSecurityConfig { @Bean public SecurityFilterChain actuatorFilterChain(HttpSecurity http) throws Exception { return http - .requestMatchers().antMatchers("/actuator/**").and() - .httpBasic().and() + .securityMatcher(AntPathRequestMatcher.antMatcher("/actuator/**")) + .httpBasic(Customizer.withDefaults()) .authenticationProvider(/* configure basic authentication users here with PasswordEncoder etc. */) - .authorizeRequests().anyRequest().authenticated().and() + .authorizeHttpRequests(r -> r.anyRequest().authenticated()) .build(); } diff --git a/menu.md b/menu.md index 2a24bc199..8e8432d11 100644 --- a/menu.md +++ b/menu.md @@ -37,7 +37,7 @@ - [HANA](guides/databases-hana) - [H2 (Java)](guides/databases-h2) - [PostgreSQL](guides/databases-postgres) -- [Querying](guides/querying) + - [Messaging](guides/messaging/) - [Authorization](guides/authorization) - [Localization, i18n](guides/i18n) @@ -73,6 +73,7 @@ - [Docker](tools/index#docker) - [CDS Editors](tools/index#cds-editor) - [CDS Lint](tools/index#cds-lint) +- [CDS Typer](tools/cds-typer) ### [CDS](cds/) @@ -137,6 +138,7 @@ - [cds. ql ...](node.js/cds-ql) - [cds. tx()](node.js/cds-tx) - [cds. log()](node.js/cds-log) +- [cds .import()](node.js/cds-dk#import) - [cds. env](node.js/cds-env) - [cds. auth](node.js/authentication) - [cds. i18n](node.js/cds-i18n) diff --git a/node.js/authentication.md b/node.js/authentication.md index 0c52569f9..1d94d7589 100644 --- a/node.js/authentication.md +++ b/node.js/authentication.md @@ -2,14 +2,13 @@ label: Authentication synopsis: > This guide is about authenticating users on incoming HTTP requests. -layout: node-js +# layout: node-js status: released uacp: This page is linked from the Help Portal at https://help.sap.com/products/BTP/65de2977205c403bbc107264b8eccf4b/29c25e504fdb4752b0383d3c407f52a6.html --- # Authentication - {{$frontmatter?.synopsis}} This is done by [authentication middlewares](#strategies) setting the [`req.user` property](#cds-user) which is then used in [authorization enforcement](#enforcement) decisions. @@ -195,7 +194,7 @@ You can optionally configure users as follows: "": { "password": "", "roles": [ "", ... ], - "userAttributes": { ... } + "attr": { ... } } } } @@ -203,20 +202,22 @@ You can optionally configure users as follows: } ``` +#### Pre-defined Mock Users {#mock-users} + The default configuration shipped with `@sap/cds` specifies these users: ```jsonc "users": { - "alice": { "roles": ["admin", "cds.Subscriber"] }, - "bob": { "roles": ["cds.ExtensionDeveloper", "cds.UIFlexDeveloper"] }, - "carol": { "roles": ["admin", "cds.Subscriber", "cds.ExtensionDeveloper", "cds.UIFlexDeveloper"] }, - "dave": { "roles": ["admin", "cds.Subscriber"] }, - "erin": { "roles": ["admin", "cds.Subscriber", "cds.ExtensionDeveloper", "cds.UIFlexDeveloper"] }, - "fred": { }, - "me": { }, - "*": true //> all other logins are allowed as well + "alice": { "tenant": "t1", "roles": [ "cds.Subscriber", "admin" ] }, + "bob": { "tenant": "t1", "roles": [ "cds.ExtensionDeveloper", "cds.UIFlexDeveloper" ] }, + "carol": { "tenant": "t1", "roles": [ "cds.Subscriber", "admin", "cds.ExtensionDeveloper", "cds.UIFlexDeveloper" ] }, + "dave": { "tenant": "t1", "roles": [ "cds.Subscriber", "admin" ], "features": [] }, + "erin": { "tenant": "t2", "roles": [ "cds.Subscriber", "admin", "cds.ExtensionDeveloper", "cds.UIFlexDeveloper" ] }, + "fred": { "tenant": "t2", "features": [ "isbn" ] }, + "me": { "tenant": "t1", "features": [ "*" ] }, + "yves": { "roles": [ "internal-user" ] } + "*": true //> all other logins are allowed as well } -} ``` ::: tip @@ -263,7 +264,7 @@ You can optionally configure users as follows: "": { "password": "", "roles": [ "", ... ], - "userAttributes": { ... } + "attr": { ... } } } } @@ -376,7 +377,7 @@ module.exports = function custom_auth (req, res, next) { // do your custom authentication req.user = new cds.User({ id: '', - roles: ['', ''] + roles: ['', ''], attr: { : '', : '' @@ -478,7 +479,7 @@ If you don’t know the API endpoint, have a look at section [Regions and API En >In that case you need to add the environment variable `cds_requires_auth_kind=xsuaa` to the run configuration. 3. Check authentication configuration: -``` +```sh cds env list requires.uaa --resolve-bindings --profile hybrid ``` This prints the full `uaa` configuration including the credentials. @@ -593,4 +594,4 @@ The login fails pointing to the correct OAuth configuration URL that is expected cf update-service bookshop-uaa -c xs-security.json ``` -3. Retry \ No newline at end of file +3. Retry diff --git a/node.js/best-practices.md b/node.js/best-practices.md index 385128087..2b75da078 100644 --- a/node.js/best-practices.md +++ b/node.js/best-practices.md @@ -406,4 +406,4 @@ srv.on('READ', 'Books', (req, next) => { }) ``` -In the returned object, `value` is an instance of [stream.Readable](https://nodejs.org/api/stream.html#class-streamreadable) and the properties `$mediaContentType`, `$mediaContentDispositionFilename`, and `$mediaContentDispositionType` are used to set the respective headers. \ No newline at end of file +In the returned object, `value` is an instance of [stream.Readable](https://nodejs.org/api/stream.html#class-streamreadable) and the properties `$mediaContentType`, `$mediaContentDispositionFilename`, and `$mediaContentDispositionType` are used to set the respective headers. diff --git a/node.js/cds-compile.md b/node.js/cds-compile.md index 20acb7575..01a6a1520 100644 --- a/node.js/cds-compile.md +++ b/node.js/cds-compile.md @@ -59,7 +59,7 @@ let csn = await cds.compile ('file:db') ### Single in-memory sources -If a single string, not starting with `file:` is passed as first argument, it is interpreted as a CDL source string and compiled to CSN synchroneously: +If a single string, not starting with `file:` is passed as first argument, it is interpreted as a CDL source string and compiled to CSN synchronously: ```js let csn = cds.compile (` diff --git a/node.js/cds-connect.md b/node.js/cds-connect.md index 045142bfc..48893674e 100644 --- a/node.js/cds-connect.md +++ b/node.js/cds-connect.md @@ -1,6 +1,6 @@ --- shorty: cds.connect -layout: node-js +# layout: node-js status: released --- @@ -572,7 +572,7 @@ For example, in the _package.json_ file: The credentials can be provided in any supported way. For example, as env variables: -``` +```sh cds_requires_myservice_credentials_user=test-user cds_requires_myservice_credentials_password=test-password ``` diff --git a/node.js/cds-dk.md b/node.js/cds-dk.md index 9f4eaf2db..744014b46 100644 --- a/node.js/cds-dk.md +++ b/node.js/cds-dk.md @@ -2,7 +2,7 @@ label: CDS Design Time synopsis: > This guide is about consuming CDS design-time APIs programmatically. -layout: node-js +# layout: node-js status: released --- diff --git a/node.js/cds-env.md b/node.js/cds-env.md index 17733da23..76f0bf225 100644 --- a/node.js/cds-env.md +++ b/node.js/cds-env.md @@ -7,7 +7,7 @@ status: released # Project-Specific Configurations -
+{{ $frontmatter.synopsis }} ## CLI `cds env` Command {#cli} @@ -95,7 +95,7 @@ As depicted in the figure below `cds.env` provides one-stop convenient and trans | 3 | [_./.cdsrc.json_](#project-settings) | static project settings | 4 | [_./package.json_](#project-settings) | static project settings → `{"cds":{ ... }}` | 5 | [_./.cdsrc-private.json_](#private-project-settings) | user-specific project config | -| 6 | [_./default-env.json_](#process-env) | *deprecated see cds bind* +| 6 | [_./default-env.json_](#process-env) | *deprecated, see cds bind* | 7 | [_./.env_](#process-env) | user-specific project env (lines of `name=value`) | 8 | [`process.env.CDS_CONFIG`](#env-cds-config) | runtime settings from shell or cloud | 9 | [`process.env`](#process-env) | runtime env vars from shell or cloud diff --git a/node.js/cds-facade.md b/node.js/cds-facade.md index ab24a9483..356278ae7 100644 --- a/node.js/cds-facade.md +++ b/node.js/cds-facade.md @@ -35,13 +35,13 @@ Welcome to cds repl v6.8.0 Many properties of cds are references to submodules, which are lazy-loaded on first access to minimize bootstrapping time and memory consumption. The submodules are documented in separate documents. -- [cds. models]() {.property} - - [cds. resolve()]() {.method} - - [cds. load()]() {.method} - - [cds. parse()]() {.method} +- [cds. models](./cds-facade.md) {.property} + - [cds. resolve()](./cds-compile.md#cds-resolve) {.method} + - [cds. load()](./cds-facade.md) {.method} + - [cds. parse()](./cds-compile.md#cds-parse) {.method} - [cds. compile](cds-compile) {.method} - [cds. linked()](cds-reflect) {.method} - - [cds. deploy()]() {.method} + - [cds. deploy()](./cds-facade.md) {.method} - [cds. server](cds-serve) {.property} - [cds. serve()](cds-serve) {.method} - cds. services {.property} @@ -107,7 +107,8 @@ Following are properties which are not references to submodules. Returns the version of the `@sap/cds` package from which the current instance of the `cds` facade module was loaded. For example, use that to write version specific code: ```js -if (cds.version[0] < 6) // code for pre cds6 usage +const [major, minor] = cds.version.split('.').map(Number) +if (major < 6) // code for pre cds6 usage ``` @@ -128,7 +129,7 @@ Returns the pathname of the `@sap/cds` installation folder from which the curren ### cds. root {.property} Returns the project root that is used by all CAP runtime file access as the root directory. -By default tihs is `process.cwd()`, but can be set to a different root folder. +By default this is `process.cwd()`, but can be set to a different root folder. It's guaranteed to be an absolute folder name. ```js @@ -335,7 +336,7 @@ cds.exit() //> will rune above handlers before stopping the server ## Lifecycle Events - +[Learn more about Lifecycle Events in `cds.server`](cds-server#lifecycle-events){.learn-more} #### cds. once '*bootstrap*' {.event} diff --git a/node.js/cds-i18n.md b/node.js/cds-i18n.md index da36f7791..5aae69103 100644 --- a/node.js/cds-i18n.md +++ b/node.js/cds-i18n.md @@ -1,6 +1,6 @@ # Localization / i18n -## +## ### Generic Errors @@ -57,7 +57,7 @@ module.exports = (srv) => { Find the current list of generic runtime texts: -``` +```txt 400=Bad Request 401=Unauthorized 403=Forbidden diff --git a/node.js/cds-log.md b/node.js/cds-log.md index 387daf8cd..4df62d73e 100644 --- a/node.js/cds-log.md +++ b/node.js/cds-log.md @@ -1,6 +1,6 @@ --- shorty: cds.log -layout: node-js +# layout: node-js subtocs: false status: released --- @@ -76,7 +76,7 @@ cds.log() → { In addition, there is a boolean indicator to check which levels are active through corresponding underscored property, for example, `LOG._debug` is true if debug is enabled. ### *Recommendations* -1. **Leave formatting to the log functions** — for example don't expensivelly construct debug messages, which aren't logged at all if debug is not switched on. For example: +1. **Leave formatting to the log functions** — for example don't expensively construct debug messages, which aren't logged at all if debug is not switched on. For example: ```js // DONT: @@ -214,7 +214,7 @@ You can assign different implementations by exchanging the factory with your own #### *Arguments* -- `label`— the log label to use with each log output, if appliccable +- `label`— the log label to use with each log output, if applicable - `level`— the log level to enable → *0=off, 1=error, 2=warn, 3=info, 4=debug, 5=trace* @@ -225,7 +225,7 @@ You can assign different implementations by exchanging the factory with your own npm add winston ``` -Being designed as a simple log facade, `cds.log` can be easily integrated with advanced logging framworks such as [`winston`](https://www.npmjs.com/package/winston). For example, using the built-in convenience method `cds.log.winstonLogger()` in your project's server.js like that: +Being designed as a simple log facade, `cds.log` can be easily integrated with advanced logging frameworks such as [`winston`](https://www.npmjs.com/package/winston). For example, using the built-in convenience method `cds.log.winstonLogger()` in your project's server.js like that: ```js cds.log.Logger = cds.log.winstonLogger() @@ -293,7 +293,7 @@ DEBUG=all cds watch Values can be - comma-separated list of [logger ids](#logger-id), or - - the value `all` to switch on all debug ouput. + - the value `all` to switch on all debug output. ### *Matching multiple values of `DEBUG`* diff --git a/node.js/cds-plugins.md b/node.js/cds-plugins.md index a9e24ef1a..4f504623a 100644 --- a/node.js/cds-plugins.md +++ b/node.js/cds-plugins.md @@ -2,7 +2,7 @@ -The `cds-plugin` technique allows to provide extension packages with auto-configuration. +The `cds-plugin` technique allows to provide extension packages with auto-configuration. [[toc]] @@ -10,7 +10,7 @@ The `cds-plugin` technique allows to provide extension packages with auto-config ## Add a `cds-plugin.js` -Simply add a file `cds-plugin.js` next to the `package.json` of your package to have this detected and loaded automatically when bootstrapping CAP Node.js servers through `cds serve`, or other CLI commands. +Simply add a file `cds-plugin.js` next to the `package.json` of your package to have this detected and loaded automatically when bootstrapping CAP Node.js servers through `cds serve`, or other CLI commands. Within such `cds-plugin.js` modules you can use [the `cds` facade](cds-facade) object, to register to lifecycle events or plugin to other parts of the framework. For example, they can react to lifecycle events, the very same way as in [custom `server.js`](cds-server#custom-server-js) modules: @@ -23,15 +23,15 @@ cds.on('served', ()=>{ ... }) ::: -Sometimes `cds-plugin.js` files can also be empty, for example if you plugin only registers new settings. +Sometimes `cds-plugin.js` files can also be empty, for example if you plugin only registers new settings. ## Auto-Configuration -Plugins can also add new config settings, thereby providing auto configuration. Simply add a `cds` section to your *package.json* file, as you would do in a project's *package.json*. +Plugins can also add new config settings, thereby providing auto configuration. Simply add a `cds` section to your *package.json* file, as you would do in a project's *package.json*. -For example, this is the configuratuion provided by the new SQLite service package `@cap-js/sqlite`: +For example, this is the configuration provided by the new SQLite service package `@cap-js/sqlite`: ::: code-group @@ -57,13 +57,13 @@ For example, this is the configuratuion provided by the new SQLite service packa ::: -In effect this automatically configures a required `db` service using the `sql` preset. This preset is configred below to use the `sqlite` preset in development. The `sqlite` preset is in turn configured below, to use the plugin package's main as implementation. +In effect this automatically configures a required `db` service using the `sql` preset. This preset is configred below to use the `sqlite` preset in development. The `sqlite` preset is in turn configured below, to use the plugin package's main as implementation. ## cds. plugins {.property} -This property refers to a module that implements the plugin machinery in cds, by fetching and loading installed plugins along these lines: +This property refers to a module that implements the plugin machinery in cds, by fetching and loading installed plugins along these lines: 1. For all entries in your *package.json*'s `dependencies` and `devDependencies` ... 2. Select all target packages having a `cds-plugin.js` file in their roots ... diff --git a/node.js/cds-ql.md b/node.js/cds-ql.md index a4cb1d36a..2eddc0476 100644 --- a/node.js/cds-ql.md +++ b/node.js/cds-ql.md @@ -162,7 +162,7 @@ SELECT ID from Books where ID=? dbc.run (sql, [201]) ``` -The only mistake you could do is to imperatively concatenate user input with CQL or SQL fragements, instead of using the tagged strings or other options promoted by `cds.ql`. For example, assumed you had written the above code sample like that: +The only mistake you could do is to imperatively concatenate user input with CQL or SQL fragments, instead of using the tagged strings or other options promoted by `cds.ql`. For example, assumed you had written the above code sample like that: ```js let input = 201 //> might be entered by end users diff --git a/node.js/cds-reflect.md b/node.js/cds-reflect.md index cfe58f833..14fe46edd 100644 --- a/node.js/cds-reflect.md +++ b/node.js/cds-reflect.md @@ -2,7 +2,7 @@ shorty: cds.reflect synopsis: > Find here information about reflecting parsed CDS models in CSN representation. -layout: node-js +# layout: node-js status: released --- diff --git a/node.js/cds-serve.md b/node.js/cds-serve.md index 3569db7bc..8243ec345 100644 --- a/node.js/cds-serve.md +++ b/node.js/cds-serve.md @@ -16,7 +16,7 @@ status: released -Use `cds.serve()` to construct service providers from the service definitions in corresponding CDS models. +Use `cds.serve()` to construct service providers from the service definitions in corresponding CDS models. Declaration: @@ -126,7 +126,7 @@ cds.serve('all').from(csn)... Allows to specify the protocol through which to expose the service. Currently supported values are: -* `'rest'` plain http rest protocol without any OData-specific extensions +* `'rest'` plain HTTP rest protocol without any OData-specific extensions * `'odata'` standard OData rest protocol without any Fiori-specific extensions * `'fiori'` OData protocol with all Fiori-specific extensions like Draft enabled @@ -205,7 +205,7 @@ srv/cat-service.js #> service implementation used by default ## cds. middlewares -For each service served at a cewrtain protocol, the framework registers a configurable set of express middlewares by default like so: +For each service served at a certain protocol, the framework registers a configurable set of express middlewares by default like so: ```js app.use (cds.middlewares.before, protocol_adapter, cds.middlewares.after) @@ -275,7 +275,7 @@ By default, the protocols are served at the following path: ### @protocol -Configures at which protocol(s) a service is served. +Configures at which protocol(s) a service is served. ```cds @odata @@ -318,4 +318,3 @@ service CatalogService {} ``` Be aware that using an absolute path will disallow serving the service at multiple protocols. - diff --git a/node.js/cds-server.md b/node.js/cds-server.md index 3018eab7b..73c9384ac 100644 --- a/node.js/cds-server.md +++ b/node.js/cds-server.md @@ -21,9 +21,9 @@ CAP Node.js servers a bootstrapped through a [built-in `server.js` module](#buil ## CLI Command `cds serve` A Node.js CAP server process is usually started with the `cds serve` CLI command, -with `cds run` and `cds watch` as convenience variants. +with `cds run` and `cds watch` as convenience variants. -**For deployment**, when the `@sap/cds-dk` package providing the `cds` CLI executable is not available, use the `cds-serve` binary provided by the `@sap/cds` package: +**For deployment**, when the `@sap/cds-dk` package providing the `cds` CLI executable is not available, use the `cds-serve` binary provided by the `@sap/cds` package: ```json { @@ -64,7 +64,7 @@ cds.server = module.exports = async function (options) { await cds.serve ('all') .from(csn) .in (app) await cds.emit ('served', cds.services) - // launch http server + // launch HTTP server cds .emit ('launching', app) const port = o.port ?? process.env.PORT || 4004 const server = app.server = app.listen(port) .once ('listening', ()=> diff --git a/node.js/cds-test.md b/node.js/cds-test.md index cac3e02a7..fb102143a 100644 --- a/node.js/cds-test.md +++ b/node.js/cds-test.md @@ -33,7 +33,7 @@ Use function `cds.test()` to easily launch and test a CAP server as follows: ```js{3} const project = __dirname+'/..' // The project's root folder -const cds = require('@sap/cds/lib') +const cds = require('@sap/cds') cds.test(project) ``` [Learn more about tests in the SFLIGHT app.](https://github.com/SAP-samples/cap-sflight/blob/main/test/odata.test.js){.learn-more} @@ -49,7 +49,7 @@ cds.test(project) By default, the `cds` APIs read files from the current working directory. To run test simulating whole projects, use `cds.test.in(<...>)` to specify the test project's root folder. ```js{2} -const cds = require('@sap/cds/lib') +const cds = require('@sap/cds') cds.test.in(__dirname) ``` diff --git a/node.js/cds-tx.md b/node.js/cds-tx.md index dee1faf86..bc1687ec3 100644 --- a/node.js/cds-tx.md +++ b/node.js/cds-tx.md @@ -1,6 +1,6 @@ --- redirect_from: node.js/transactions -layout: node-js +# layout: node-js status: released label: Transactions --- @@ -87,7 +87,7 @@ Nested transactions are automatically committed when their root transaction is c ## Manual Transactions -Use `cds.tx()` to start and commit transactions manually, if you need to ensure two or more queries to run in a single transaction. The easiest way to achive this is shown below: +Use `cds.tx()` to start and commit transactions manually, if you need to ensure two or more queries to run in a single transaction. The easiest way to achieve this is shown below: ```js cds.tx (async ()=>{ @@ -135,7 +135,7 @@ cds.spawn ({ user: cds.User.privileged, every: 1000 /* ms */ }, async ()=>{ ## Event Contexts -Automatic transaction management, as offered by the CAP, needs access to properties of the invocation context — most prominently, the current **user** and **tenant**, or the inbound http request object. +Automatic transaction management, as offered by the CAP, needs access to properties of the invocation context — most prominently, the current **user** and **tenant**, or the inbound HTTP request object. #### Accessing Context Information {.h2} @@ -148,7 +148,7 @@ if (user.is('admin')) ... ``` ```js -// Accessing http req, res objects +// Accessing HTTP req, res objects const { req, res } = cds.context.http if (!req.is('application/json')) res.send(415) ``` @@ -200,11 +200,11 @@ cds.context.user.id === 'u1' //> true ```tsx function srv.tx ( ctx?, fn? : tx => {...} ) => Promise function srv.tx ( ctx? ) => tx -var ctx : { tenant, user, locale } +var ctx : { tenant, user, locale } ``` -Use this method to run the given function `fn` and all nested operations in a new *root* transaction. -For example: +Use this method to run the given function `fn` and all nested operations in a new *root* transaction. +For example: ```js await srv.tx (async tx => { @@ -214,15 +214,15 @@ await srv.tx (async tx => { }) ``` -::: details Transaction objects `tx` +::: details Transaction objects `tx` -The `tx` object created by `srv.tx()` and passed to the function `fn` is a derivate of the service instance, constructed like that: +The `tx` object created by `srv.tx()` and passed to the function `fn` is a derivate of the service instance, constructed like that: ```js -tx = { __proto__:srv, - context: { tenant, user, locale }, // defaults from cds.context +tx = { __proto__:srv, + context: { tenant, user, locale }, // defaults from cds.context model: cds.model, // could be a tenant-extended variant instead - commit(){...}, + commit(){...}, rollback(){...}, } ``` @@ -231,7 +231,7 @@ tx = { __proto__:srv, -The new root transaction is also active for all nested operations run from fn, including other services, most important database services. In particular, the following would work as well as expected (this time using `cds.tx` as shortcut `cds.db.tx`): +The new root transaction is also active for all nested operations run from fn, including other services, most important database services. In particular, the following would work as well as expected (this time using `cds.tx` as shortcut `cds.db.tx`): ```js await cds.tx (async () => { @@ -267,7 +267,7 @@ try { // [!code focus] ::: warning -Note though, that with this usage we've **not** started a new async context, and all nested calls to other services, like db, will **not** happen within the confines of the constructed `tx`. +Note though, that with this usage we've **not** started a new async context, and all nested calls to other services, like db, will **not** happen within the confines of the constructed `tx`. ::: diff --git a/node.js/cds-utils.md b/node.js/cds-utils.md index 16802263f..ab5b6a61a 100644 --- a/node.js/cds-utils.md +++ b/node.js/cds-utils.md @@ -125,7 +125,7 @@ if (dir) { } ``` -Returns `undefined` or a fully resolved absolute filename of the existing directory, including recursivels resolving symbolic links. Relative fileames are resolved in relation to [`cds.root`](cds-facade#cds-root), +Returns `undefined` or a fully resolved absolute filename of the existing directory, including recursively resolving symbolic links. Relative fileames are resolved in relation to [`cds.root`](cds-facade#cds-root), @@ -139,7 +139,7 @@ let file = isdir ('package.json') let json = fs.readFileSync (file,'utf8') ``` -Returns `undefined` or a fully resolved absolute filename of the existing directory, including recursivels resolving symbolic links. Relative fileames are resolved in relation to [`cds.root`](cds-facade#cds-root), +Returns `undefined` or a fully resolved absolute filename of the existing directory, including recursively resolving symbolic links. Relative fileames are resolved in relation to [`cds.root`](cds-facade#cds-root), @@ -152,7 +152,7 @@ const { read } = cds.utils let pkg = await read ('package.json') ``` -Relative fileames are resolved in relation to [`cds.root`](cds-facade#cds-root). The iplementation uses `utf8` encoding by default. If the file is a `.json` file, the read content is automatically `JSON.parse`d. +Relative fileames are resolved in relation to [`cds.root`](cds-facade#cds-root). The implementation uses `utf8` encoding by default. If the file is a `.json` file, the read content is automatically `JSON.parse`d. diff --git a/node.js/core-services.md b/node.js/core-services.md index cdf7cada5..0f020b1ee 100644 --- a/node.js/core-services.md +++ b/node.js/core-services.md @@ -6,13 +6,6 @@ uacp: This page is linked from the Help Portal at https://help.sap.com/products/ # Core Services - -::: tip -This is an overhauled documentation for `cds.Service`→ find the old one [here](services.md). -::: - - - [[toc]] @@ -452,7 +445,7 @@ class BooksService extends cds.ApplicationService { } ``` -Ensure to call `super.init()` to allow subclassses to register their handlers. Do that after your registrations to go before the ones from subclasses, or before to have theirs go before yours. +Ensure to call `super.init()` to allow subclasses to register their handlers. Do that after your registrations to go before the ones from subclasses, or before to have theirs go before yours. @@ -703,7 +696,7 @@ function srv.on (event, handler: ( *Find details on `event` in [srv.on,before,after()](#srv-on-before-after) above*. {.learn-more} -Handlers for asynchronous events, as emitted by [`srv.emit()`](#srv-emit-event), are registered in the same way as [`.on(request)`](#srv-on-request) handlers for synchrounous requests, but work slightly different: +Handlers for asynchronous events, as emitted by [`srv.emit()`](#srv-emit-event), are registered in the same way as [`.on(request)`](#srv-on-request) handlers for synchronous requests, but work slightly different: 1. They are usually registered 'from the outside', not as part of a service's implementation. 2. They receive only a single argument: `msg`, an instance of [`cds.Event`](./events.md#cds-request); no `next`. @@ -1026,7 +1019,7 @@ Promise.seq = handlers => async function next(){ }() ``` -All matching `before`, `on`, and `after` handlers are executed in corresponding phases, with the next phase being started only if no `req.errors` have occured. In addition, note that... +All matching `before`, `on`, and `after` handlers are executed in corresponding phases, with the next phase being started only if no `req.errors` have occurred. In addition, note that... - **`before`** handlers are always executed *concurrently* - **`on`** handlers are executed... diff --git a/node.js/databases.md b/node.js/databases.md index c8ff0d30a..5ca8eb773 100644 --- a/node.js/databases.md +++ b/node.js/databases.md @@ -2,13 +2,13 @@ label: Databases synopsis: > Class cds.DatabaseService and subclasses thereof are technical services representing persistent storage. -layout: node-js +# layout: node-js status: released --- # Databases -
+{{ $frontmatter.synopsis }} ## cds.**DatabaseService** class { #cds-db-service} @@ -66,6 +66,10 @@ By default, the following [pool configuration](https://www.npmjs.com/package/gen } ``` +::: warning +This default pool configuration does not apply to `@cap-js` database implementations. +::: + The _generic-pool_ has a built-in pool evictor, which inspects idle database connections in the pool and destroys them if they are too old. The following parameters are provided in the pool configuration: @@ -104,6 +108,7 @@ Pool configuration can be adjusted by setting the `pool` option as shown in the } } ``` + ::: warning _❗ Warning_ The parameters are very specific to the current technical setup, such as the application environment and database location. Even though we provide a default pool configuration, we expect that each application provides its own configuration based on its specific needs. @@ -153,4 +158,4 @@ In contrast to the Java runtime, deep upserts and delta payloads are not yet sup ## More to Come This documentation is not complete yet, or the APIs are not released for general availability. -There's more to come in this place in upcoming releases. \ No newline at end of file +Stay tuned to upcoming releases for further updates. diff --git a/node.js/events.md b/node.js/events.md index 4b0e30c48..0b0764413 100644 --- a/node.js/events.md +++ b/node.js/events.md @@ -67,7 +67,7 @@ this.on ('*', req => { }) ``` -In addition, you can acess the current event context from wherever you are in your code via the continuation-local variable [`cds.context`](#cds-context): +In addition, you can access the current event context from wherever you are in your code via the continuation-local variable [`cds.context`](#cds-context): ```js let { tenant, user } = cds.context @@ -79,7 +79,7 @@ In addition, you can acess the current event context from wherever you are in yo ### . http {.property} -If the inbound process came from an http channel, this property provides access to express's common [`req`](https://expressjs.com/en/4x/api.html#req) and [`res`](https://expressjs.com/en/4x/api.html#res) objects. The property is propagated from `cds.context` to all child requests. So, on all handlers, even the ones in your database services, you can always access that property like so: +If the inbound process came from an HTTP channel, this property provides access to express's common [`req`](https://expressjs.com/en/4x/api.html#req) and [`res`](https://expressjs.com/en/4x/api.html#res) objects. The property is propagated from `cds.context` to all child requests. So, on all handlers, even the ones in your database services, you can always access that property like so: ```js this.on ('*', req => { @@ -105,7 +105,7 @@ For inbound HTTP requests the implementation fills it from these sources in orde On outgoing HTTP messages it is propagated as `x-correlation-id` header. -For inbound [CloudEvents](https://cloudevents.io) messages it taken from [the `id` context property](https://github.com/cloudevents/spec/blob/v1.0.1/spec.md#id) and propagated to the same on ougoing CloudEvents messages. +For inbound [CloudEvents](https://cloudevents.io) messages it taken from [the `id` context property](https://github.com/cloudevents/spec/blob/v1.0.1/spec.md#id) and propagated to the same on outgoing CloudEvents messages. diff --git a/node.js/fiori.md b/node.js/fiori.md index ce6d3c7c6..61a276028 100644 --- a/node.js/fiori.md +++ b/node.js/fiori.md @@ -1,112 +1,89 @@ -# Fiori Support +--- +status: released +--- +# Fiori Support -[[toc]] +See [Advanced > Draft-based Editing](../advanced/fiori#draft-support) for an overview on SAP Fiori Draft support in CAP. +[[toc]] + +## Lean Draft -## Draft Support - -Class `ApplicationService` provides built-in support for Fiori Draft, which add these additional CRUD events: - -You can add your validation logic in the before operation handler for the `CREATE` or `UPDATE` event (as in the case of nondraft implementations) or on the `SAVE` event (specific to drafts only): - -```js -srv.before ('NEW','Books.drafts', ...) // run before creating new drafts -srv.after ('NEW','Books.drafts', ...) // for newly created drafts -srv.after ('EDIT','Books', ...) // for starting edit draft sessions -srv.before ('PATCH','Books.drafts', ...) // for field-level validations during editing -srv.before ('SAVE','Books.drafts', ...) // run at final save only -``` - -These events get triggered during the draft edit session whenever the user tabs from one field to the next, and can be used to provide early feedback. - - - -### Event: `'NEW'` - -```tsx -function srv.on ('NEW', .drafts, req => {...}) -``` - -Starts a draft session with an empty draft entity. - - - -### Event: `'EDIT'` - -```tsx -function srv.on ('EDIT', , req => {...}) -``` - -Starts a draft session with a draft entity copied from an existing active entity. - - - -### Event: `'PATCH'` - -```tsx -function srv.on ('PATCH', .drafts, req => {...}) -function srv.on ('PATCH', , req => {...}) -``` - -Reacts on PATCH events on draft entities. - -Same event can go to active entities, bypassing draft mechanism, but respecting draft locks. - - - -### Event: `'SAVE'` - -```tsx -function srv.on ('SAVE', .drafts, req => {...}) -``` - -Ends a draft session by UPDATEing the active entity with draft entity data. - +Lean draft is a new approach which makes it easier to differentiate between drafts and active instances in your code. This new architecture drastically reduces the complexity and enables more features like storing active instances in remote systems while keeping the corresponding drafts in the local persistence. +### Enablement -### Event: `'CANCEL'` +Lean draft is enabled by default. Add this to your `cds` configuration to disable the feature: -```tsx -function srv.on ('CANCEL', .drafts, req => {...}) +```json +{ + "cds": { + "fiori": { + "lean_draft": false + } + } +} ``` -Ends a draft session by canceling, i.e., deleting the draft entity. +### Handlers Registration {#draft-support} +Class `ApplicationService` provides built-in support for Fiori Draft. All CRUD events are supported for both, active and draft entities. +Please note that draft-enabled entities must follow a specific draft choreography. +The examples are provided for `on` handlers, but the same is true for `before` and `after` handlers. + ```js + // only active entities + srv.on(['CREATE', 'READ', 'UPDATE', 'DELETE'], 'MyEntity', /*...*/) + // only draft entities + srv.on(['CREATE', 'READ', 'UPDATE', 'DELETE'], 'MyEntity.drafts', /*...*/) + // bound action/function on active entity + srv.on('boundActionOrFunction', 'MyEntity', /*...*/) + // bound action/function on draft entity + srv.on('boundActionOrFunction', 'MyEntity.drafts', /*...*/) + ``` +It's also possible to use the array variant to register a handler for both entities, for example: `srv.on('boundActionOrFunction', ['MyEntity', 'MyEntity.drafts'], /*...*/)`. -## Draft Locks +Additionally, you can add your logic to the draft-specific events as follows: + ```js + // When a new draft is created + srv.on('NEW', 'MyEntity.drafts', /*...*/) + // When a draft is discarded + srv.on('CANCEL', 'MyEntity.drafts', /*...*/) + // When a new draft is created from an active instance + srv.on('EDIT', 'MyEntity', /*...*/) + // When the active entity is changed + srv.on('SAVE', 'MyEntity', /*...*/) + ``` +- The `CANCEL` event is triggered when you cancel the draft. In this case, the draft entity is deleted and the active entity isn't changed. +- The `EDIT` event is triggered when you start editing an active entity. As a result `MyEntity.drafts` is created. +- The `SAVE` event is just a shortcut for `['UPDATE', 'CREATE']` on an active entity. This event is also triggered when you press the `SAVE` button in UI after finishing editing your draft. Note, that composition children of the active entity will also be updated or created. -## Lean Draft - -Lean draft is a new approach which makes it easier to differentiate between drafts and active instances in your code. This new architecture drastically reduces the complexity and enables more features like storing active instances in remote systems while keeping the corresponding drafts in the local persistence. +::: info Compatibility flag + For compatibility to previous variants, set `cds.fiori.draft_compat` to `true`. +::: -### Enablement +### Draft Locks -Lean draft is enabled by default. Add this to your `cds` configuration to disable the feature: +To prevent inconsistency, the entities with draft are locked for modifications by other users. The lock is released when the draft is saved, canceled or a timeout is hit. The default timeout is 15 minutes. You can configure this timeout by the following application configuration property: ```json -{ - "cds": { - "fiori": { - "lean_draft": false - } - } -} +cds.drafts.cancellationTimeout=1h ``` ### Differences to Previous Version @@ -118,27 +95,8 @@ Lean draft is enabled by default. Add this to your `cds` configuration to disabl MyEntity.drafts // points to model.definitions['MyEntity.drafts'] ``` -- Handlers must be registered for the correct entity, the following variants are allowed: - - ```js - srv.on(['CREATE', 'READ', 'UPDATE', 'DELETE'], 'MyEntity', /*...*/) - srv.on(['CREATE', 'READ', 'UPDATE', 'DELETE'], 'MyEntity.drafts', /*...*/) - srv.on('boundActionOrFunction', 'MyEntity', /*...*/) - srv.on('boundActionOrFunction', 'MyEntity.drafts', /*...*/) - srv.on('NEW', 'MyEntity.drafts', /*...*/) - srv.on('CANCEL', 'MyEntity.drafts', /*...*/) - srv.on('EDIT', 'MyEntity', /*...*/) - srv.on('SAVE', 'MyEntity', /*...*/) - ``` - - You can use the array variant to register handler for both, for example: `srv.on('boundActionOrFunction', ['MyEntity', 'MyEntity.drafts'], /*...*/)`. - - ::: info Compatibility flag - For compatibility to previous variants, set `cds.fiori.draft_compat` to `true`. - ::: - - - Queries are now cleansed from draft-related properties (like `IsActiveEntity`) +- `PATCH` event isn't supported anymore. - The target is resolved before the handler execution and points to either the active or draft entity: ```js @@ -155,3 +113,4 @@ Lean draft is enabled by default. Add this to your `cds` configuration to disabl - Draft-related properties (with the exception of `IsActiveEntity`) are only computed for the target entity, not for expanded sub entities since this is not required by Fiori Elements. - Manual filtering on draft-related properties is not allowed, only certain draft scenarios are supported. + diff --git a/node.js/messaging.md b/node.js/messaging.md index c696cbd0b..e79b7454a 100644 --- a/node.js/messaging.md +++ b/node.js/messaging.md @@ -2,7 +2,7 @@ synopsis: > Learn details about using messaging services and outbox for asynchronous communications. redirect_from: node.js/outbox -layout: node-js +# layout: node-js status: released --- diff --git a/node.js/middlewares.md b/node.js/middlewares.md index f22547e90..6c85984d4 100644 --- a/node.js/middlewares.md +++ b/node.js/middlewares.md @@ -2,7 +2,7 @@ label: Middlewares synopsis: > Learn about default middlewares and all the options of customization. -layout: node-js +# layout: node-js status: released --- diff --git a/node.js/protocols.md b/node.js/protocols.md index 5ab00d35e..af59d2e8a 100644 --- a/node.js/protocols.md +++ b/node.js/protocols.md @@ -2,7 +2,7 @@ label: Protocols synopsis: > Protocol Adapters translate inbound requests and messages into instances of [cds.Request](events) and cds.Query. -layout: node-js +# layout: node-js status: released --- diff --git a/node.js/remote-services.md b/node.js/remote-services.md index 21b0a466a..e148e39ac 100644 --- a/node.js/remote-services.md +++ b/node.js/remote-services.md @@ -2,7 +2,7 @@ label: Remote Services synopsis: > Class `cds.RemoteService` is a service proxy class to consume remote services via different [protocols](protocols), like OData or plain REST. -layout: node-js +# layout: node-js status: released --- diff --git a/node.js/services.md b/node.js/services.md index 43f83cacb..7a1745c7d 100644 --- a/node.js/services.md +++ b/node.js/services.md @@ -1,7 +1,7 @@ --- shorty: Class cds.Service -layout: node-js -status: released +# layout: node-js +# status: released uacp: This page is linked from the Help Portal at https://help.sap.com/products/BTP/65de2977205c403bbc107264b8eccf4b/29c25e504fdb4752b0383d3c407f52a6.html --- @@ -641,7 +641,7 @@ cds.serve('cat-service') .with (function(){ #### srv.on ('error', (err, req) => {}) {#srv-on-error} -Using the special event name `error`, you can register a custom error handler that is invoked whenever an error will be returned to the client. The handler receives the error object `err` and the respective request object `req`. Only synchroneous modifications of the error object are allowed. +Using the special event name `error`, you can register a custom error handler that is invoked whenever an error will be returned to the client. The handler receives the error object `err` and the respective request object `req`. Only synchronous modifications of the error object are allowed. **Examples:** @@ -1007,7 +1007,7 @@ _**Common Usages:**_
-`srv.send` can be used in a manner similar to [`srv.emit`](#srv-send) to send requests using [HTTP methods](events#method) and additional request `headers` if neccessary: +`srv.send` can be used in a manner similar to [`srv.emit`](#srv-send) to send requests using [HTTP methods](events#method) and additional request `headers` if necessary: ```js const srv = await cds.connect.to('SomeService') @@ -1070,7 +1070,7 @@ These methods are _HTTP method-style_ counterparts to the [_CRUD-style_ convenie As with these, the returned queries can be executed with `await`. For invoking actions or functions, do not use `.post()` or `.get()` but see [Actions API](#srv-action). -REST and OData services support these basic http methods, which are mapped to their CRUD counterpart as documented in the following. They can be used to construct data queries as with the CRUD variants as well as be used to send plain HTTP requests. +REST and OData services support these basic HTTP methods, which are mapped to their CRUD counterpart as documented in the following. They can be used to construct data queries as with the CRUD variants as well as be used to send plain HTTP requests. _**Common Usages:**_ diff --git a/node.js/typescript.md b/node.js/typescript.md index b21e825e2..56e88f6e4 100644 --- a/node.js/typescript.md +++ b/node.js/typescript.md @@ -1,5 +1,5 @@ --- -layout: node-js +# layout: node-js status: released redirect_from: get-started/using-typescript --- @@ -109,10 +109,23 @@ We invite you to contribute and help us complete the typings as appropriate. Sou -## Enhanced TypeScript Support +## Generating Model Types Automatically -CAP envisions to enhance the TypeScript support and, for example, generate TypeScript interfaces for definitions in CDS models. -But it's important to know, that this isn't planned in our roadmap (yet). +The [`cds-typer` package](https://www.npmjs.com/package/@cap-js/cds-typer) offers a way to derive TypeScript definitions from a CDS model to give you enhanced code completion and a certain degree of type safety when implementing services. -There are already community contributions that fill in this gap.
-See the [blog post by Simon Gaudeck](https://blogs.sap.com/2020/05/22/taking-cap-to-the-next-level-with-typescript/) for more details. +```js +class CatalogService extends cds.ApplicationService { init() { + const { Book } = require('#cds-models/sap/capire/bookshop') + + this.before('CREATE', Book, req => { + req.data.… // known to be a Book. Code completion suggests: + // ID (number) + // title (string) + // author (Author) + // createdAt (Date) + // … + }) +}} +``` + +You can find extensive documentation in a [dedicated chapter](../tools/cds-typer), together with a [quickstart guide](../tools/cds-typer#cds-typer-vscode) to get everything up and running. diff --git a/package-lock.json b/package-lock.json index b7308a91d..589ce7f51 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { - "name": "cap-docs", - "version": "0.2.0", + "name": "@cap-js/docs", + "version": "0.8.0", "lockfileVersion": 3, "requires": true, "packages": { "": { - "name": "cap-docs", - "version": "0.2.0", + "name": "@cap-js/docs", + "version": "0.8.0", "license": "SEE LICENSE IN LICENSE", "devDependencies": { "@types/adm-zip": "^0.5.0", @@ -16,39 +16,47 @@ "eslint-plugin-vue": "^9.11.0", "gray-matter": "^4.0.3", "sass": "^1.62.1", - "sitemap": "^7.1.1", - "vitepress": "^1.0.0-beta.2" + "vitepress": "^1.0.0-rc.4" + } + }, + "node_modules/@aashutoshrathi/word-wrap": { + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/@aashutoshrathi/word-wrap/-/word-wrap-1.2.6.tgz", + "integrity": "sha512-1Yjs2SvM8TflER/OD3cOjhWWOZb58A2t7wpE2S9XfBYTiIl+XFhQG2bjy4Pu1I+EAlCNUzRDYDdFwFYUKvXcIA==", + "dev": true, + "engines": { + "node": ">=0.10.0" } }, "node_modules/@algolia/autocomplete-core": { - "version": "1.9.2", - "resolved": "https://registry.npmjs.org/@algolia/autocomplete-core/-/autocomplete-core-1.9.2.tgz", - "integrity": "sha512-hkG80c9kx9ClVAEcUJbTd2ziVC713x9Bji9Ty4XJfKXlxlsx3iXsoNhAwfeR4ulzIUg7OE5gez0UU1zVDdG7kg==", + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/@algolia/autocomplete-core/-/autocomplete-core-1.9.3.tgz", + "integrity": "sha512-009HdfugtGCdC4JdXUbVJClA0q0zh24yyePn+KUGk3rP7j8FEe/m5Yo/z65gn6nP/cM39PxpzqKrL7A6fP6PPw==", "dev": true, "dependencies": { - "@algolia/autocomplete-plugin-algolia-insights": "1.9.2", - "@algolia/autocomplete-shared": "1.9.2" + "@algolia/autocomplete-plugin-algolia-insights": "1.9.3", + "@algolia/autocomplete-shared": "1.9.3" } }, "node_modules/@algolia/autocomplete-plugin-algolia-insights": { - "version": "1.9.2", - "resolved": "https://registry.npmjs.org/@algolia/autocomplete-plugin-algolia-insights/-/autocomplete-plugin-algolia-insights-1.9.2.tgz", - "integrity": "sha512-2LVsf4W66hVHQ3Ua/8k15oPlxjELCztbAkQm/hP42Sw+GLkHAdY1vaVRYziaWq64+Oljfg6FKkZHCdgXH+CGIA==", + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/@algolia/autocomplete-plugin-algolia-insights/-/autocomplete-plugin-algolia-insights-1.9.3.tgz", + "integrity": "sha512-a/yTUkcO/Vyy+JffmAnTWbr4/90cLzw+CC3bRbhnULr/EM0fGNvM13oQQ14f2moLMcVDyAx/leczLlAOovhSZg==", "dev": true, "dependencies": { - "@algolia/autocomplete-shared": "1.9.2" + "@algolia/autocomplete-shared": "1.9.3" }, "peerDependencies": { "search-insights": ">= 1 < 3" } }, "node_modules/@algolia/autocomplete-preset-algolia": { - "version": "1.9.2", - "resolved": "https://registry.npmjs.org/@algolia/autocomplete-preset-algolia/-/autocomplete-preset-algolia-1.9.2.tgz", - "integrity": "sha512-pqgIm2GNqtCT59Y1ICctIPrYTi34+wNPiNWEclD/yDzp5uDUUsyGe5XrUjCNyQRTKonAlmYxoaEHOn8FWgmBHA==", + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/@algolia/autocomplete-preset-algolia/-/autocomplete-preset-algolia-1.9.3.tgz", + "integrity": "sha512-d4qlt6YmrLMYy95n5TB52wtNDr6EgAIPH81dvvvW8UmuWRgxEtY0NJiPwl/h95JtG2vmRM804M0DSwMCNZlzRA==", "dev": true, "dependencies": { - "@algolia/autocomplete-shared": "1.9.2" + "@algolia/autocomplete-shared": "1.9.3" }, "peerDependencies": { "@algolia/client-search": ">= 4.9.1 < 6", @@ -56,9 +64,9 @@ } }, "node_modules/@algolia/autocomplete-shared": { - "version": "1.9.2", - "resolved": "https://registry.npmjs.org/@algolia/autocomplete-shared/-/autocomplete-shared-1.9.2.tgz", - "integrity": "sha512-XxX6YDn+7LG+SmdpXEOnj7fc3TjiVpQ0CbGhjLwrd2tYr6LVY2D4Iiu/iuYJ4shvVDWWnpwArSk0uIWC/8OPUA==", + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/@algolia/autocomplete-shared/-/autocomplete-shared-1.9.3.tgz", + "integrity": "sha512-Wnm9E4Ye6Rl6sTTqjoymD+l8DjSTHsHboVRYrKgEt8Q7UHm9nYbqhN/i0fhUYA3OAEH7WA8x3jfpnmJm3rKvaQ==", "dev": true, "peerDependencies": { "@algolia/client-search": ">= 4.9.1 < 6", @@ -66,138 +74,138 @@ } }, "node_modules/@algolia/cache-browser-local-storage": { - "version": "4.17.2", - "resolved": "https://registry.npmjs.org/@algolia/cache-browser-local-storage/-/cache-browser-local-storage-4.17.2.tgz", - "integrity": "sha512-ZkVN7K/JE+qMQbpR6h3gQOGR6yCJpmucSBCmH5YDxnrYbp2CbrVCu0Nr+FGVoWzMJNznj1waShkfQ9awERulLw==", + "version": "4.19.1", + "resolved": "https://registry.npmjs.org/@algolia/cache-browser-local-storage/-/cache-browser-local-storage-4.19.1.tgz", + "integrity": "sha512-FYAZWcGsFTTaSAwj9Std8UML3Bu8dyWDncM7Ls8g+58UOe4XYdlgzXWbrIgjaguP63pCCbMoExKr61B+ztK3tw==", "dev": true, "dependencies": { - "@algolia/cache-common": "4.17.2" + "@algolia/cache-common": "4.19.1" } }, "node_modules/@algolia/cache-common": { - "version": "4.17.2", - "resolved": "https://registry.npmjs.org/@algolia/cache-common/-/cache-common-4.17.2.tgz", - "integrity": "sha512-fojbhYIS8ovfYs6hwZpy1O4mBfVRxNgAaZRqsdVQd54hU4MxYDYFCxagYX28lOBz7btcDHld6BMoWXvjzkx6iQ==", + "version": "4.19.1", + "resolved": "https://registry.npmjs.org/@algolia/cache-common/-/cache-common-4.19.1.tgz", + "integrity": "sha512-XGghi3l0qA38HiqdoUY+wvGyBsGvKZ6U3vTiMBT4hArhP3fOGLXpIINgMiiGjTe4FVlTa5a/7Zf2bwlIHfRqqg==", "dev": true }, "node_modules/@algolia/cache-in-memory": { - "version": "4.17.2", - "resolved": "https://registry.npmjs.org/@algolia/cache-in-memory/-/cache-in-memory-4.17.2.tgz", - "integrity": "sha512-UYQcMzPurNi+cPYkuPemTZkjKAjdgAS1hagC5irujKbrYnN4yscK4TkOI5tX+O8/KegtJt3kOK07OIrJ2QDAAw==", + "version": "4.19.1", + "resolved": "https://registry.npmjs.org/@algolia/cache-in-memory/-/cache-in-memory-4.19.1.tgz", + "integrity": "sha512-+PDWL+XALGvIginigzu8oU6eWw+o76Z8zHbBovWYcrtWOEtinbl7a7UTt3x3lthv+wNuFr/YD1Gf+B+A9V8n5w==", "dev": true, "dependencies": { - "@algolia/cache-common": "4.17.2" + "@algolia/cache-common": "4.19.1" } }, "node_modules/@algolia/client-account": { - "version": "4.17.2", - "resolved": "https://registry.npmjs.org/@algolia/client-account/-/client-account-4.17.2.tgz", - "integrity": "sha512-doSk89pBPDpDyKJSHFADIGa2XSGrBCj3QwPvqtRJXDADpN+OjW+eTR8r4hEs/7X4GGfjfAOAES8JgDx+fZntYw==", + "version": "4.19.1", + "resolved": "https://registry.npmjs.org/@algolia/client-account/-/client-account-4.19.1.tgz", + "integrity": "sha512-Oy0ritA2k7AMxQ2JwNpfaEcgXEDgeyKu0V7E7xt/ZJRdXfEpZcwp9TOg4TJHC7Ia62gIeT2Y/ynzsxccPw92GA==", "dev": true, "dependencies": { - "@algolia/client-common": "4.17.2", - "@algolia/client-search": "4.17.2", - "@algolia/transporter": "4.17.2" + "@algolia/client-common": "4.19.1", + "@algolia/client-search": "4.19.1", + "@algolia/transporter": "4.19.1" } }, "node_modules/@algolia/client-analytics": { - "version": "4.17.2", - "resolved": "https://registry.npmjs.org/@algolia/client-analytics/-/client-analytics-4.17.2.tgz", - "integrity": "sha512-V+DcXbOtD/hKwAR3qGQrtlrJ3q2f9OKfx843q744o4m3xHv5ueCAvGXB1znPsdaUrVDNAImcgEgqwI9x7EJbDw==", + "version": "4.19.1", + "resolved": "https://registry.npmjs.org/@algolia/client-analytics/-/client-analytics-4.19.1.tgz", + "integrity": "sha512-5QCq2zmgdZLIQhHqwl55ZvKVpLM3DNWjFI4T+bHr3rGu23ew2bLO4YtyxaZeChmDb85jUdPDouDlCumGfk6wOg==", "dev": true, "dependencies": { - "@algolia/client-common": "4.17.2", - "@algolia/client-search": "4.17.2", - "@algolia/requester-common": "4.17.2", - "@algolia/transporter": "4.17.2" + "@algolia/client-common": "4.19.1", + "@algolia/client-search": "4.19.1", + "@algolia/requester-common": "4.19.1", + "@algolia/transporter": "4.19.1" } }, "node_modules/@algolia/client-common": { - "version": "4.17.2", - "resolved": "https://registry.npmjs.org/@algolia/client-common/-/client-common-4.17.2.tgz", - "integrity": "sha512-gKBUnjxi0ukJYIJxVREYGt1Dmj1B3RBYbfGWi0dIPp1BC1VvQm+BOuNwsIwmq/x3MPO+sGuK978eKiP3tZDvag==", + "version": "4.19.1", + "resolved": "https://registry.npmjs.org/@algolia/client-common/-/client-common-4.19.1.tgz", + "integrity": "sha512-3kAIVqTcPrjfS389KQvKzliC559x+BDRxtWamVJt8IVp7LGnjq+aVAXg4Xogkur1MUrScTZ59/AaUd5EdpyXgA==", "dev": true, "dependencies": { - "@algolia/requester-common": "4.17.2", - "@algolia/transporter": "4.17.2" + "@algolia/requester-common": "4.19.1", + "@algolia/transporter": "4.19.1" } }, "node_modules/@algolia/client-personalization": { - "version": "4.17.2", - "resolved": "https://registry.npmjs.org/@algolia/client-personalization/-/client-personalization-4.17.2.tgz", - "integrity": "sha512-wc4UgOWxSYWz5wpuelNmlt895jA9twjZWM2ms17Ws8qCvBHF7OVGdMGgbysPB8790YnfvvDnSsWOv3CEj26Eow==", + "version": "4.19.1", + "resolved": "https://registry.npmjs.org/@algolia/client-personalization/-/client-personalization-4.19.1.tgz", + "integrity": "sha512-8CWz4/H5FA+krm9HMw2HUQenizC/DxUtsI5oYC0Jxxyce1vsr8cb1aEiSJArQT6IzMynrERif1RVWLac1m36xw==", "dev": true, "dependencies": { - "@algolia/client-common": "4.17.2", - "@algolia/requester-common": "4.17.2", - "@algolia/transporter": "4.17.2" + "@algolia/client-common": "4.19.1", + "@algolia/requester-common": "4.19.1", + "@algolia/transporter": "4.19.1" } }, "node_modules/@algolia/client-search": { - "version": "4.17.2", - "resolved": "https://registry.npmjs.org/@algolia/client-search/-/client-search-4.17.2.tgz", - "integrity": "sha512-FUjIs+gRe0upJC++uVs4sdxMw15JxfkT86Gr/kqVwi9kcqaZhXntSbW/Fw959bIYXczjmeVQsilYvBWW4YvSZA==", + "version": "4.19.1", + "resolved": "https://registry.npmjs.org/@algolia/client-search/-/client-search-4.19.1.tgz", + "integrity": "sha512-mBecfMFS4N+yK/p0ZbK53vrZbL6OtWMk8YmnOv1i0LXx4pelY8TFhqKoTit3NPVPwoSNN0vdSN9dTu1xr1XOVw==", "dev": true, "dependencies": { - "@algolia/client-common": "4.17.2", - "@algolia/requester-common": "4.17.2", - "@algolia/transporter": "4.17.2" + "@algolia/client-common": "4.19.1", + "@algolia/requester-common": "4.19.1", + "@algolia/transporter": "4.19.1" } }, "node_modules/@algolia/logger-common": { - "version": "4.17.2", - "resolved": "https://registry.npmjs.org/@algolia/logger-common/-/logger-common-4.17.2.tgz", - "integrity": "sha512-EfXuweUE+1HiSMsQidaDWA5Lv4NnStYIlh7PO5pLkI+sdhbMX0e5AO5nUAMIFM1VkEANes70RA8fzhP6OqCqQQ==", + "version": "4.19.1", + "resolved": "https://registry.npmjs.org/@algolia/logger-common/-/logger-common-4.19.1.tgz", + "integrity": "sha512-i6pLPZW/+/YXKis8gpmSiNk1lOmYCmRI6+x6d2Qk1OdfvX051nRVdalRbEcVTpSQX6FQAoyeaui0cUfLYW5Elw==", "dev": true }, "node_modules/@algolia/logger-console": { - "version": "4.17.2", - "resolved": "https://registry.npmjs.org/@algolia/logger-console/-/logger-console-4.17.2.tgz", - "integrity": "sha512-JuG8HGVlJ+l/UEDK4h2Y8q/IEmRjQz1J0aS9tf6GPNbGYiSvMr1DDdZ+hqV3bb1XE6wU8Ypex56HisWMSpnG0A==", + "version": "4.19.1", + "resolved": "https://registry.npmjs.org/@algolia/logger-console/-/logger-console-4.19.1.tgz", + "integrity": "sha512-jj72k9GKb9W0c7TyC3cuZtTr0CngLBLmc8trzZlXdfvQiigpUdvTi1KoWIb2ZMcRBG7Tl8hSb81zEY3zI2RlXg==", "dev": true, "dependencies": { - "@algolia/logger-common": "4.17.2" + "@algolia/logger-common": "4.19.1" } }, "node_modules/@algolia/requester-browser-xhr": { - "version": "4.17.2", - "resolved": "https://registry.npmjs.org/@algolia/requester-browser-xhr/-/requester-browser-xhr-4.17.2.tgz", - "integrity": "sha512-FKI2lYWwksALfRt2OETFmGb5+P7WVc4py2Ai3H7k8FSfTLwVvs9WVVmtlx6oANQ8RFEK4B85h8DQJTJ29TDfmA==", + "version": "4.19.1", + "resolved": "https://registry.npmjs.org/@algolia/requester-browser-xhr/-/requester-browser-xhr-4.19.1.tgz", + "integrity": "sha512-09K/+t7lptsweRTueHnSnmPqIxbHMowejAkn9XIcJMLdseS3zl8ObnS5GWea86mu3vy4+8H+ZBKkUN82Zsq/zg==", "dev": true, "dependencies": { - "@algolia/requester-common": "4.17.2" + "@algolia/requester-common": "4.19.1" } }, "node_modules/@algolia/requester-common": { - "version": "4.17.2", - "resolved": "https://registry.npmjs.org/@algolia/requester-common/-/requester-common-4.17.2.tgz", - "integrity": "sha512-Rfim23ztAhYpE9qm+KCfCRo+YLJCjiiTG+IpDdzUjMpYPhUtirQT0A35YEd/gKn86YNyydxS9w8iRSjwKh+L0A==", + "version": "4.19.1", + "resolved": "https://registry.npmjs.org/@algolia/requester-common/-/requester-common-4.19.1.tgz", + "integrity": "sha512-BisRkcWVxrDzF1YPhAckmi2CFYK+jdMT60q10d7z3PX+w6fPPukxHRnZwooiTUrzFe50UBmLItGizWHP5bDzVQ==", "dev": true }, "node_modules/@algolia/requester-node-http": { - "version": "4.17.2", - "resolved": "https://registry.npmjs.org/@algolia/requester-node-http/-/requester-node-http-4.17.2.tgz", - "integrity": "sha512-E0b0kyCDMvUIhQmDNd/mH4fsKJdEEX6PkMKrYJjzm6moo+rP22tqpq4Rfe7DZD8OB6/LsDD3zs3Kvd+L+M5wwQ==", + "version": "4.19.1", + "resolved": "https://registry.npmjs.org/@algolia/requester-node-http/-/requester-node-http-4.19.1.tgz", + "integrity": "sha512-6DK52DHviBHTG2BK/Vv2GIlEw7i+vxm7ypZW0Z7vybGCNDeWzADx+/TmxjkES2h15+FZOqVf/Ja677gePsVItA==", "dev": true, "dependencies": { - "@algolia/requester-common": "4.17.2" + "@algolia/requester-common": "4.19.1" } }, "node_modules/@algolia/transporter": { - "version": "4.17.2", - "resolved": "https://registry.npmjs.org/@algolia/transporter/-/transporter-4.17.2.tgz", - "integrity": "sha512-m8pXlz5OnNzjD1rcw+duCN4jG4yEzkJBsvKYMoN22Oq6rQwy1AY5muZ+IQUs4dL+A364CYkRMLRWhvXpCZ1x+g==", + "version": "4.19.1", + "resolved": "https://registry.npmjs.org/@algolia/transporter/-/transporter-4.19.1.tgz", + "integrity": "sha512-nkpvPWbpuzxo1flEYqNIbGz7xhfhGOKGAZS7tzC+TELgEmi7z99qRyTfNSUlW7LZmB3ACdnqAo+9A9KFBENviQ==", "dev": true, "dependencies": { - "@algolia/cache-common": "4.17.2", - "@algolia/logger-common": "4.17.2", - "@algolia/requester-common": "4.17.2" + "@algolia/cache-common": "4.19.1", + "@algolia/logger-common": "4.19.1", + "@algolia/requester-common": "4.19.1" } }, "node_modules/@babel/parser": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.22.5.tgz", - "integrity": "sha512-DFZMC9LJUG9PLOclRC32G63UXwzqS2koQC8dkx+PLdmt1xSePYpbT/NbsrJy8Q/muXz7o/h/d4A7Fuyixm559Q==", + "version": "7.22.10", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.22.10.tgz", + "integrity": "sha512-lNbdGsQb9ekfsnjFGhEiF4hfFqGgfOP3H3d27re3n+CGhNuTSUEQdfWk556sTLNTloczcdM5TYF2LhzmDQKyvQ==", "dev": true, "bin": { "parser": "bin/babel-parser.js" @@ -207,30 +215,30 @@ } }, "node_modules/@docsearch/css": { - "version": "3.5.0", - "resolved": "https://registry.npmjs.org/@docsearch/css/-/css-3.5.0.tgz", - "integrity": "sha512-Ob5FQLubplcBNihAVtriR59FRBeP8u69F6mu4L4yIr60KfsPc10bOV0DoPErJw0zF9IBN2cNLW9qdmt8zWPxyg==", + "version": "3.5.1", + "resolved": "https://registry.npmjs.org/@docsearch/css/-/css-3.5.1.tgz", + "integrity": "sha512-2Pu9HDg/uP/IT10rbQ+4OrTQuxIWdKVUEdcw9/w7kZJv9NeHS6skJx1xuRiFyoGKwAzcHXnLp7csE99sj+O1YA==", "dev": true }, "node_modules/@docsearch/js": { - "version": "3.5.0", - "resolved": "https://registry.npmjs.org/@docsearch/js/-/js-3.5.0.tgz", - "integrity": "sha512-WqB+z+zVKSXDkGq028nClT9RvMzfFlemZuIulX5ZwWkdUtl4k7M9cmZA/c6kuZf7FG24XQsMHWuBjeUo9hLRyA==", + "version": "3.5.1", + "resolved": "https://registry.npmjs.org/@docsearch/js/-/js-3.5.1.tgz", + "integrity": "sha512-EXi8de5njxgP6TV3N9ytnGRLG9zmBNTEZjR4VzwPcpPLbZxxTLG2gaFyJyKiFVQxHW/DPlMrDJA3qoRRGEkgZw==", "dev": true, "dependencies": { - "@docsearch/react": "3.5.0", + "@docsearch/react": "3.5.1", "preact": "^10.0.0" } }, "node_modules/@docsearch/react": { - "version": "3.5.0", - "resolved": "https://registry.npmjs.org/@docsearch/react/-/react-3.5.0.tgz", - "integrity": "sha512-3IG8mmSMzSHNGy2S1VuPyYU9tFCxFpj5Ov8SYwsSHM4yMvFsaO9oFxXocA5lSenliIELhuOuS5+BdxHa/Qlf2A==", + "version": "3.5.1", + "resolved": "https://registry.npmjs.org/@docsearch/react/-/react-3.5.1.tgz", + "integrity": "sha512-t5mEODdLzZq4PTFAm/dvqcvZFdPDMdfPE5rJS5SC8OUq9mPzxEy6b+9THIqNM9P0ocCb4UC5jqBrxKclnuIbzQ==", "dev": true, "dependencies": { - "@algolia/autocomplete-core": "1.9.2", - "@algolia/autocomplete-preset-algolia": "1.9.2", - "@docsearch/css": "3.5.0", + "@algolia/autocomplete-core": "1.9.3", + "@algolia/autocomplete-preset-algolia": "1.9.3", + "@docsearch/css": "3.5.1", "algoliasearch": "^4.0.0" }, "peerDependencies": { @@ -251,9 +259,9 @@ } }, "node_modules/@esbuild/android-arm": { - "version": "0.17.19", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.17.19.tgz", - "integrity": "sha512-rIKddzqhmav7MSmoFCmDIb6e2W57geRsM94gV2l38fzhXMwq7hZoClug9USI2pFRGL06f4IOPHHpFNOkWieR8A==", + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.18.20.tgz", + "integrity": "sha512-fyi7TDI/ijKKNZTUJAQqiG5T7YjJXgnzkURqmGj13C6dCqckZBLdl4h7bkhHt/t0WP+zO9/zwroDvANaOqO5Sw==", "cpu": [ "arm" ], @@ -267,9 +275,9 @@ } }, "node_modules/@esbuild/android-arm64": { - "version": "0.17.19", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.17.19.tgz", - "integrity": "sha512-KBMWvEZooR7+kzY0BtbTQn0OAYY7CsiydT63pVEaPtVYF0hXbUaOyZog37DKxK7NF3XacBJOpYT4adIJh+avxA==", + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.18.20.tgz", + "integrity": "sha512-Nz4rJcchGDtENV0eMKUNa6L12zz2zBDXuhj/Vjh18zGqB44Bi7MBMSXjgunJgjRhCmKOjnPuZp4Mb6OKqtMHLQ==", "cpu": [ "arm64" ], @@ -283,9 +291,9 @@ } }, "node_modules/@esbuild/android-x64": { - "version": "0.17.19", - "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.17.19.tgz", - "integrity": "sha512-uUTTc4xGNDT7YSArp/zbtmbhO0uEEK9/ETW29Wk1thYUJBz3IVnvgEiEwEa9IeLyvnpKrWK64Utw2bgUmDveww==", + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.18.20.tgz", + "integrity": "sha512-8GDdlePJA8D6zlZYJV/jnrRAi6rOiNaCC/JclcXpB+KIuvfBN4owLtgzY2bsxnx666XjJx2kDPUmnTtR8qKQUg==", "cpu": [ "x64" ], @@ -299,9 +307,9 @@ } }, "node_modules/@esbuild/darwin-arm64": { - "version": "0.17.19", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.17.19.tgz", - "integrity": "sha512-80wEoCfF/hFKM6WE1FyBHc9SfUblloAWx6FJkFWTWiCoht9Mc0ARGEM47e67W9rI09YoUxJL68WHfDRYEAvOhg==", + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.18.20.tgz", + "integrity": "sha512-bxRHW5kHU38zS2lPTPOyuyTm+S+eobPUnTNkdJEfAddYgEcll4xkT8DB9d2008DtTbl7uJag2HuE5NZAZgnNEA==", "cpu": [ "arm64" ], @@ -315,9 +323,9 @@ } }, "node_modules/@esbuild/darwin-x64": { - "version": "0.17.19", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.17.19.tgz", - "integrity": "sha512-IJM4JJsLhRYr9xdtLytPLSH9k/oxR3boaUIYiHkAawtwNOXKE8KoU8tMvryogdcT8AU+Bflmh81Xn6Q0vTZbQw==", + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.18.20.tgz", + "integrity": "sha512-pc5gxlMDxzm513qPGbCbDukOdsGtKhfxD1zJKXjCCcU7ju50O7MeAZ8c4krSJcOIJGFR+qx21yMMVYwiQvyTyQ==", "cpu": [ "x64" ], @@ -331,9 +339,9 @@ } }, "node_modules/@esbuild/freebsd-arm64": { - "version": "0.17.19", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.17.19.tgz", - "integrity": "sha512-pBwbc7DufluUeGdjSU5Si+P3SoMF5DQ/F/UmTSb8HXO80ZEAJmrykPyzo1IfNbAoaqw48YRpv8shwd1NoI0jcQ==", + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.18.20.tgz", + "integrity": "sha512-yqDQHy4QHevpMAaxhhIwYPMv1NECwOvIpGCZkECn8w2WFHXjEwrBn3CeNIYsibZ/iZEUemj++M26W3cNR5h+Tw==", "cpu": [ "arm64" ], @@ -347,9 +355,9 @@ } }, "node_modules/@esbuild/freebsd-x64": { - "version": "0.17.19", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.17.19.tgz", - "integrity": "sha512-4lu+n8Wk0XlajEhbEffdy2xy53dpR06SlzvhGByyg36qJw6Kpfk7cp45DR/62aPH9mtJRmIyrXAS5UWBrJT6TQ==", + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.18.20.tgz", + "integrity": "sha512-tgWRPPuQsd3RmBZwarGVHZQvtzfEBOreNuxEMKFcd5DaDn2PbBxfwLcj4+aenoh7ctXcbXmOQIn8HI6mCSw5MQ==", "cpu": [ "x64" ], @@ -363,9 +371,9 @@ } }, "node_modules/@esbuild/linux-arm": { - "version": "0.17.19", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.17.19.tgz", - "integrity": "sha512-cdmT3KxjlOQ/gZ2cjfrQOtmhG4HJs6hhvm3mWSRDPtZ/lP5oe8FWceS10JaSJC13GBd4eH/haHnqf7hhGNLerA==", + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.18.20.tgz", + "integrity": "sha512-/5bHkMWnq1EgKr1V+Ybz3s1hWXok7mDFUMQ4cG10AfW3wL02PSZi5kFpYKrptDsgb2WAJIvRcDm+qIvXf/apvg==", "cpu": [ "arm" ], @@ -379,9 +387,9 @@ } }, "node_modules/@esbuild/linux-arm64": { - "version": "0.17.19", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.17.19.tgz", - "integrity": "sha512-ct1Tg3WGwd3P+oZYqic+YZF4snNl2bsnMKRkb3ozHmnM0dGWuxcPTTntAF6bOP0Sp4x0PjSF+4uHQ1xvxfRKqg==", + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.18.20.tgz", + "integrity": "sha512-2YbscF+UL7SQAVIpnWvYwM+3LskyDmPhe31pE7/aoTMFKKzIc9lLbyGUpmmb8a8AixOL61sQ/mFh3jEjHYFvdA==", "cpu": [ "arm64" ], @@ -395,9 +403,9 @@ } }, "node_modules/@esbuild/linux-ia32": { - "version": "0.17.19", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.17.19.tgz", - "integrity": "sha512-w4IRhSy1VbsNxHRQpeGCHEmibqdTUx61Vc38APcsRbuVgK0OPEnQ0YD39Brymn96mOx48Y2laBQGqgZ0j9w6SQ==", + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.18.20.tgz", + "integrity": "sha512-P4etWwq6IsReT0E1KHU40bOnzMHoH73aXp96Fs8TIT6z9Hu8G6+0SHSw9i2isWrD2nbx2qo5yUqACgdfVGx7TA==", "cpu": [ "ia32" ], @@ -411,9 +419,9 @@ } }, "node_modules/@esbuild/linux-loong64": { - "version": "0.17.19", - "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.17.19.tgz", - "integrity": "sha512-2iAngUbBPMq439a+z//gE+9WBldoMp1s5GWsUSgqHLzLJ9WoZLZhpwWuym0u0u/4XmZ3gpHmzV84PonE+9IIdQ==", + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.18.20.tgz", + "integrity": "sha512-nXW8nqBTrOpDLPgPY9uV+/1DjxoQ7DoB2N8eocyq8I9XuqJ7BiAMDMf9n1xZM9TgW0J8zrquIb/A7s3BJv7rjg==", "cpu": [ "loong64" ], @@ -427,9 +435,9 @@ } }, "node_modules/@esbuild/linux-mips64el": { - "version": "0.17.19", - "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.17.19.tgz", - "integrity": "sha512-LKJltc4LVdMKHsrFe4MGNPp0hqDFA1Wpt3jE1gEyM3nKUvOiO//9PheZZHfYRfYl6AwdTH4aTcXSqBerX0ml4A==", + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.18.20.tgz", + "integrity": "sha512-d5NeaXZcHp8PzYy5VnXV3VSd2D328Zb+9dEq5HE6bw6+N86JVPExrA6O68OPwobntbNJ0pzCpUFZTo3w0GyetQ==", "cpu": [ "mips64el" ], @@ -443,9 +451,9 @@ } }, "node_modules/@esbuild/linux-ppc64": { - "version": "0.17.19", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.17.19.tgz", - "integrity": "sha512-/c/DGybs95WXNS8y3Ti/ytqETiW7EU44MEKuCAcpPto3YjQbyK3IQVKfF6nbghD7EcLUGl0NbiL5Rt5DMhn5tg==", + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.18.20.tgz", + "integrity": "sha512-WHPyeScRNcmANnLQkq6AfyXRFr5D6N2sKgkFo2FqguP44Nw2eyDlbTdZwd9GYk98DZG9QItIiTlFLHJHjxP3FA==", "cpu": [ "ppc64" ], @@ -459,9 +467,9 @@ } }, "node_modules/@esbuild/linux-riscv64": { - "version": "0.17.19", - "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.17.19.tgz", - "integrity": "sha512-FC3nUAWhvFoutlhAkgHf8f5HwFWUL6bYdvLc/TTuxKlvLi3+pPzdZiFKSWz/PF30TB1K19SuCxDTI5KcqASJqA==", + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.18.20.tgz", + "integrity": "sha512-WSxo6h5ecI5XH34KC7w5veNnKkju3zBRLEQNY7mv5mtBmrP/MjNBCAlsM2u5hDBlS3NGcTQpoBvRzqBcRtpq1A==", "cpu": [ "riscv64" ], @@ -475,9 +483,9 @@ } }, "node_modules/@esbuild/linux-s390x": { - "version": "0.17.19", - "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.17.19.tgz", - "integrity": "sha512-IbFsFbxMWLuKEbH+7sTkKzL6NJmG2vRyy6K7JJo55w+8xDk7RElYn6xvXtDW8HCfoKBFK69f3pgBJSUSQPr+4Q==", + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.18.20.tgz", + "integrity": "sha512-+8231GMs3mAEth6Ja1iK0a1sQ3ohfcpzpRLH8uuc5/KVDFneH6jtAJLFGafpzpMRO6DzJ6AvXKze9LfFMrIHVQ==", "cpu": [ "s390x" ], @@ -491,9 +499,9 @@ } }, "node_modules/@esbuild/linux-x64": { - "version": "0.17.19", - "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.17.19.tgz", - "integrity": "sha512-68ngA9lg2H6zkZcyp22tsVt38mlhWde8l3eJLWkyLrp4HwMUr3c1s/M2t7+kHIhvMjglIBrFpncX1SzMckomGw==", + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.18.20.tgz", + "integrity": "sha512-UYqiqemphJcNsFEskc73jQ7B9jgwjWrSayxawS6UVFZGWrAAtkzjxSqnoclCXxWtfwLdzU+vTpcNYhpn43uP1w==", "cpu": [ "x64" ], @@ -507,9 +515,9 @@ } }, "node_modules/@esbuild/netbsd-x64": { - "version": "0.17.19", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.17.19.tgz", - "integrity": "sha512-CwFq42rXCR8TYIjIfpXCbRX0rp1jo6cPIUPSaWwzbVI4aOfX96OXY8M6KNmtPcg7QjYeDmN+DD0Wp3LaBOLf4Q==", + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.18.20.tgz", + "integrity": "sha512-iO1c++VP6xUBUmltHZoMtCUdPlnPGdBom6IrO4gyKPFFVBKioIImVooR5I83nTew5UOYrk3gIJhbZh8X44y06A==", "cpu": [ "x64" ], @@ -523,9 +531,9 @@ } }, "node_modules/@esbuild/openbsd-x64": { - "version": "0.17.19", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.17.19.tgz", - "integrity": "sha512-cnq5brJYrSZ2CF6c35eCmviIN3k3RczmHz8eYaVlNasVqsNY+JKohZU5MKmaOI+KkllCdzOKKdPs762VCPC20g==", + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.18.20.tgz", + "integrity": "sha512-e5e4YSsuQfX4cxcygw/UCPIEP6wbIL+se3sxPdCiMbFLBWu0eiZOJ7WoD+ptCLrmjZBK1Wk7I6D/I3NglUGOxg==", "cpu": [ "x64" ], @@ -539,9 +547,9 @@ } }, "node_modules/@esbuild/sunos-x64": { - "version": "0.17.19", - "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.17.19.tgz", - "integrity": "sha512-vCRT7yP3zX+bKWFeP/zdS6SqdWB8OIpaRq/mbXQxTGHnIxspRtigpkUcDMlSCOejlHowLqII7K2JKevwyRP2rg==", + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.18.20.tgz", + "integrity": "sha512-kDbFRFp0YpTQVVrqUd5FTYmWo45zGaXe0X8E1G/LKFC0v8x0vWrhOWSLITcCn63lmZIxfOMXtCfti/RxN/0wnQ==", "cpu": [ "x64" ], @@ -555,9 +563,9 @@ } }, "node_modules/@esbuild/win32-arm64": { - "version": "0.17.19", - "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.17.19.tgz", - "integrity": "sha512-yYx+8jwowUstVdorcMdNlzklLYhPxjniHWFKgRqH7IFlUEa0Umu3KuYplf1HUZZ422e3NU9F4LGb+4O0Kdcaag==", + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.18.20.tgz", + "integrity": "sha512-ddYFR6ItYgoaq4v4JmQQaAI5s7npztfV4Ag6NrhiaW0RrnOXqBkgwZLofVTlq1daVTQNhtI5oieTvkRPfZrePg==", "cpu": [ "arm64" ], @@ -571,9 +579,9 @@ } }, "node_modules/@esbuild/win32-ia32": { - "version": "0.17.19", - "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.17.19.tgz", - "integrity": "sha512-eggDKanJszUtCdlVs0RB+h35wNlb5v4TWEkq4vZcmVt5u/HiDZrTXe2bWFQUez3RgNHwx/x4sk5++4NSSicKkw==", + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.18.20.tgz", + "integrity": "sha512-Wv7QBi3ID/rROT08SABTS7eV4hX26sVduqDOTe1MvGMjNd3EjOz4b7zeexIR62GTIEKrfJXKL9LFxTYgkyeu7g==", "cpu": [ "ia32" ], @@ -587,9 +595,9 @@ } }, "node_modules/@esbuild/win32-x64": { - "version": "0.17.19", - "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.17.19.tgz", - "integrity": "sha512-lAhycmKnVOuRYNtRtatQR1LPQf2oYCkRGkSFnseDAKPl8lu5SOsK/e1sXe5a0Pc5kHIHe6P2I/ilntNv2xf3cA==", + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.18.20.tgz", + "integrity": "sha512-kTdfRcSiDfQca/y9QIkng02avJ+NCaQvrMejlsB3RRv5sE9rRoeBPISaZpKxHELzRxZyLvNts1P27W3wV+8geQ==", "cpu": [ "x64" ], @@ -618,23 +626,23 @@ } }, "node_modules/@eslint-community/regexpp": { - "version": "4.5.1", - "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.5.1.tgz", - "integrity": "sha512-Z5ba73P98O1KUYCCJTUeVpja9RcGoMdncZ6T49FCUl2lN38JtCJ+3WgIDBv0AuY4WChU5PmtJmOCTlN6FZTFKQ==", + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.6.2.tgz", + "integrity": "sha512-pPTNuaAG3QMH+buKyBIGJs3g/S5y0caxw0ygM3YyE6yJFySwiGGSzA+mM3KJ8QQvzeLh3blwgSonkFjgQdxzMw==", "dev": true, "engines": { "node": "^12.0.0 || ^14.0.0 || >=16.0.0" } }, "node_modules/@eslint/eslintrc": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.0.3.tgz", - "integrity": "sha512-+5gy6OQfk+xx3q0d6jGZZC3f3KzAkXc/IanVxd1is/VIIziRqqt3ongQz0FiTUXqTk0c7aDB3OaFuKnuSoJicQ==", + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.1.1.tgz", + "integrity": "sha512-9t7ZA7NGGK8ckelF0PQCfcxIUzs1Md5rrO6U/c+FIQNanea5UZC0wqKXH4vHBccmu4ZJgZ2idtPeW7+Q2npOEA==", "dev": true, "dependencies": { "ajv": "^6.12.4", "debug": "^4.3.2", - "espree": "^9.5.2", + "espree": "^9.6.0", "globals": "^13.19.0", "ignore": "^5.2.0", "import-fresh": "^3.2.1", @@ -650,9 +658,9 @@ } }, "node_modules/@eslint/js": { - "version": "8.42.0", - "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.42.0.tgz", - "integrity": "sha512-6SWlXpWU5AvId8Ac7zjzmIOqMOba/JWY8XZ4A7q7Gn1Vlfg/SFFIlrtHXt9nPn4op9ZPAkl91Jao+QQv3r/ukw==", + "version": "8.46.0", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.46.0.tgz", + "integrity": "sha512-a8TLtmPi8xzPkCbp/OGFUo5yhRkHM2Ko9kOWP4znJr0WAhWyThaw3PnwX4vOTWOAMsV2uRt32PPDcEz63esSaA==", "dev": true, "engines": { "node": "^12.22.0 || ^14.17.0 || >=16.0.0" @@ -742,20 +750,11 @@ } }, "node_modules/@types/node": { - "version": "20.3.0", - "resolved": "https://registry.npmjs.org/@types/node/-/node-20.3.0.tgz", - "integrity": "sha512-cumHmIAf6On83X7yP+LrsEyUOf/YlociZelmpRYaGFydoaPdxdt80MAbu6vWerQT2COCp2nPvHdsbD7tHn/YlQ==", + "version": "20.4.9", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.4.9.tgz", + "integrity": "sha512-8e2HYcg7ohnTUbHk8focoklEQYvemQmu9M/f43DZVx43kHn0tE3BY/6gSDxS7k0SprtS0NHvj+L80cGLnoOUcQ==", "dev": true }, - "node_modules/@types/sax": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/@types/sax/-/sax-1.2.4.tgz", - "integrity": "sha512-pSAff4IAxJjfAXUG6tFkO7dsSbTmf8CtUpfhhZ5VhkRpC4628tJhh3+V6H1E+/Gs9piSzYKT5yzHO5M4GG9jkw==", - "dev": true, - "dependencies": { - "@types/node": "*" - } - }, "node_modules/@types/web-bluetooth": { "version": "0.0.17", "resolved": "https://registry.npmjs.org/@types/web-bluetooth/-/web-bluetooth-0.0.17.tgz", @@ -763,14 +762,14 @@ "dev": true }, "node_modules/@typescript-eslint/parser": { - "version": "5.59.11", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-5.59.11.tgz", - "integrity": "sha512-s9ZF3M+Nym6CAZEkJJeO2TFHHDsKAM3ecNkLuH4i4s8/RCPnF5JRip2GyviYkeEAcwGMJxkqG9h2dAsnA1nZpA==", + "version": "5.62.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-5.62.0.tgz", + "integrity": "sha512-VlJEV0fOQ7BExOsHYAGrgbEiZoi8D+Bl2+f6V2RrXerRSylnp+ZBHmPvaIa8cz0Ajx7WO7Z5RqfgYg7ED1nRhA==", "dev": true, "dependencies": { - "@typescript-eslint/scope-manager": "5.59.11", - "@typescript-eslint/types": "5.59.11", - "@typescript-eslint/typescript-estree": "5.59.11", + "@typescript-eslint/scope-manager": "5.62.0", + "@typescript-eslint/types": "5.62.0", + "@typescript-eslint/typescript-estree": "5.62.0", "debug": "^4.3.4" }, "engines": { @@ -790,13 +789,13 @@ } }, "node_modules/@typescript-eslint/scope-manager": { - "version": "5.59.11", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-5.59.11.tgz", - "integrity": "sha512-dHFOsxoLFtrIcSj5h0QoBT/89hxQONwmn3FOQ0GOQcLOOXm+MIrS8zEAhs4tWl5MraxCY3ZJpaXQQdFMc2Tu+Q==", + "version": "5.62.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-5.62.0.tgz", + "integrity": "sha512-VXuvVvZeQCQb5Zgf4HAxc04q5j+WrNAtNh9OwCsCgpKqESMTu3tF/jhZ3xG6T4NZwWl65Bg8KuS2uEvhSfLl0w==", "dev": true, "dependencies": { - "@typescript-eslint/types": "5.59.11", - "@typescript-eslint/visitor-keys": "5.59.11" + "@typescript-eslint/types": "5.62.0", + "@typescript-eslint/visitor-keys": "5.62.0" }, "engines": { "node": "^12.22.0 || ^14.17.0 || >=16.0.0" @@ -807,9 +806,9 @@ } }, "node_modules/@typescript-eslint/types": { - "version": "5.59.11", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.59.11.tgz", - "integrity": "sha512-epoN6R6tkvBYSc+cllrz+c2sOFWkbisJZWkOE+y3xHtvYaOE6Wk6B8e114McRJwFRjGvYdJwLXQH5c9osME/AA==", + "version": "5.62.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.62.0.tgz", + "integrity": "sha512-87NVngcbVXUahrRTqIK27gD2t5Cu1yuCXxbLcFtCzZGlfyVWWh8mLHkoxzjsB6DDNnvdL+fW8MiwPEJyGJQDgQ==", "dev": true, "engines": { "node": "^12.22.0 || ^14.17.0 || >=16.0.0" @@ -820,13 +819,13 @@ } }, "node_modules/@typescript-eslint/typescript-estree": { - "version": "5.59.11", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-5.59.11.tgz", - "integrity": "sha512-YupOpot5hJO0maupJXixi6l5ETdrITxeo5eBOeuV7RSKgYdU3G5cxO49/9WRnJq9EMrB7AuTSLH/bqOsXi7wPA==", + "version": "5.62.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-5.62.0.tgz", + "integrity": "sha512-CmcQ6uY7b9y694lKdRB8FEel7JbU/40iSAPomu++SjLMntB+2Leay2LO6i8VnJk58MtE9/nQSFIH6jpyRWyYzA==", "dev": true, "dependencies": { - "@typescript-eslint/types": "5.59.11", - "@typescript-eslint/visitor-keys": "5.59.11", + "@typescript-eslint/types": "5.62.0", + "@typescript-eslint/visitor-keys": "5.62.0", "debug": "^4.3.4", "globby": "^11.1.0", "is-glob": "^4.0.3", @@ -847,12 +846,12 @@ } }, "node_modules/@typescript-eslint/visitor-keys": { - "version": "5.59.11", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.59.11.tgz", - "integrity": "sha512-KGYniTGG3AMTuKF9QBD7EIrvufkB6O6uX3knP73xbKLMpH+QRPcgnCxjWXSHjMRuOxFLovljqQgQpR0c7GvjoA==", + "version": "5.62.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.62.0.tgz", + "integrity": "sha512-07ny+LHRzQXepkGg6w0mFY41fVUNBrL2Roj/++7V1txKugfjm/Ci/qSND03r2RhlJhJYMcTn9AhhSSqQp0Ysyw==", "dev": true, "dependencies": { - "@typescript-eslint/types": "5.59.11", + "@typescript-eslint/types": "5.62.0", "eslint-visitor-keys": "^3.3.0" }, "engines": { @@ -995,15 +994,15 @@ "dev": true }, "node_modules/@vueuse/core": { - "version": "10.1.2", - "resolved": "https://registry.npmjs.org/@vueuse/core/-/core-10.1.2.tgz", - "integrity": "sha512-roNn8WuerI56A5uiTyF/TEYX0Y+VKlhZAF94unUfdhbDUI+NfwQMn4FUnUscIRUhv3344qvAghopU4bzLPNFlA==", + "version": "10.3.0", + "resolved": "https://registry.npmjs.org/@vueuse/core/-/core-10.3.0.tgz", + "integrity": "sha512-BEM5yxcFKb5btFjTSAFjTu5jmwoW66fyV9uJIP4wUXXU8aR5Hl44gndaaXp7dC5HSObmgbnR2RN+Un1p68Mf5Q==", "dev": true, "dependencies": { "@types/web-bluetooth": "^0.0.17", - "@vueuse/metadata": "10.1.2", - "@vueuse/shared": "10.1.2", - "vue-demi": ">=0.14.0" + "@vueuse/metadata": "10.3.0", + "@vueuse/shared": "10.3.0", + "vue-demi": ">=0.14.5" }, "funding": { "url": "https://github.com/sponsors/antfu" @@ -1036,14 +1035,14 @@ } }, "node_modules/@vueuse/integrations": { - "version": "10.1.2", - "resolved": "https://registry.npmjs.org/@vueuse/integrations/-/integrations-10.1.2.tgz", - "integrity": "sha512-wUpG3Wv6LiWerOwCzOAM0iGhNQ4vfFUTkhj/xQy7TLXduh2M3D8N08aS0KqlxsejY6R8NLxydDIM+68QfHZZ8Q==", + "version": "10.3.0", + "resolved": "https://registry.npmjs.org/@vueuse/integrations/-/integrations-10.3.0.tgz", + "integrity": "sha512-Jgiv7oFyIgC6BxmDtiyG/fxyGysIds00YaY7sefwbhCZ2/tjEx1W/1WcsISSJPNI30in28+HC2J4uuU8184ekg==", "dev": true, "dependencies": { - "@vueuse/core": "10.1.2", - "@vueuse/shared": "10.1.2", - "vue-demi": ">=0.14.0" + "@vueuse/core": "10.3.0", + "@vueuse/shared": "10.3.0", + "vue-demi": ">=0.14.5" }, "funding": { "url": "https://github.com/sponsors/antfu" @@ -1128,21 +1127,21 @@ } }, "node_modules/@vueuse/metadata": { - "version": "10.1.2", - "resolved": "https://registry.npmjs.org/@vueuse/metadata/-/metadata-10.1.2.tgz", - "integrity": "sha512-3mc5BqN9aU2SqBeBuWE7ne4OtXHoHKggNgxZR2K+zIW4YLsy6xoZ4/9vErQs6tvoKDX6QAqm3lvsrv0mczAwIQ==", + "version": "10.3.0", + "resolved": "https://registry.npmjs.org/@vueuse/metadata/-/metadata-10.3.0.tgz", + "integrity": "sha512-Ema3YhNOa4swDsV0V7CEY5JXvK19JI/o1szFO1iWxdFg3vhdFtCtSTP26PCvbUpnUtNHBY2wx5y3WDXND5Pvnw==", "dev": true, "funding": { "url": "https://github.com/sponsors/antfu" } }, "node_modules/@vueuse/shared": { - "version": "10.1.2", - "resolved": "https://registry.npmjs.org/@vueuse/shared/-/shared-10.1.2.tgz", - "integrity": "sha512-1uoUTPBlgyscK9v6ScGeVYDDzlPSFXBlxuK7SfrDGyUTBiznb3mNceqhwvZHjtDRELZEN79V5uWPTF1VDV8svA==", + "version": "10.3.0", + "resolved": "https://registry.npmjs.org/@vueuse/shared/-/shared-10.3.0.tgz", + "integrity": "sha512-kGqCTEuFPMK4+fNWy6dUOiYmxGcUbtznMwBZLC1PubidF4VZY05B+Oht7Jh7/6x4VOWGpvu3R37WHi81cKpiqg==", "dev": true, "dependencies": { - "vue-demi": ">=0.14.0" + "vue-demi": ">=0.14.5" }, "funding": { "url": "https://github.com/sponsors/antfu" @@ -1175,9 +1174,9 @@ } }, "node_modules/acorn": { - "version": "8.8.2", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.8.2.tgz", - "integrity": "sha512-xjIYgE8HBrkpd/sJqOGNspf8uHG+NOHGOw6a/Urj8taM2EXfdNAH2oFcPeIFfsv3+kz/mJrS5VuMqbNLjCa2vw==", + "version": "8.10.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.10.0.tgz", + "integrity": "sha512-F0SAmZ8iUtS//m8DmCTA0jlh6TDKkHQyK6xc6V4KDTyZKA9dnvX9/3sRTVQrWm79glUAZbnmmNcdYwUIHWVybw==", "dev": true, "bin": { "acorn": "bin/acorn" @@ -1221,25 +1220,25 @@ } }, "node_modules/algoliasearch": { - "version": "4.17.2", - "resolved": "https://registry.npmjs.org/algoliasearch/-/algoliasearch-4.17.2.tgz", - "integrity": "sha512-VFu43JJNYIW74awp7oeQcQsPcxOhd8psqBDTfyNO2Zt6L1NqnNMTVnaIdQ+8dtKqUDBqQZp0szPxECvX8CK2Fg==", - "dev": true, - "dependencies": { - "@algolia/cache-browser-local-storage": "4.17.2", - "@algolia/cache-common": "4.17.2", - "@algolia/cache-in-memory": "4.17.2", - "@algolia/client-account": "4.17.2", - "@algolia/client-analytics": "4.17.2", - "@algolia/client-common": "4.17.2", - "@algolia/client-personalization": "4.17.2", - "@algolia/client-search": "4.17.2", - "@algolia/logger-common": "4.17.2", - "@algolia/logger-console": "4.17.2", - "@algolia/requester-browser-xhr": "4.17.2", - "@algolia/requester-common": "4.17.2", - "@algolia/requester-node-http": "4.17.2", - "@algolia/transporter": "4.17.2" + "version": "4.19.1", + "resolved": "https://registry.npmjs.org/algoliasearch/-/algoliasearch-4.19.1.tgz", + "integrity": "sha512-IJF5b93b2MgAzcE/tuzW0yOPnuUyRgGAtaPv5UUywXM8kzqfdwZTO4sPJBzoGz1eOy6H9uEchsJsBFTELZSu+g==", + "dev": true, + "dependencies": { + "@algolia/cache-browser-local-storage": "4.19.1", + "@algolia/cache-common": "4.19.1", + "@algolia/cache-in-memory": "4.19.1", + "@algolia/client-account": "4.19.1", + "@algolia/client-analytics": "4.19.1", + "@algolia/client-common": "4.19.1", + "@algolia/client-personalization": "4.19.1", + "@algolia/client-search": "4.19.1", + "@algolia/logger-common": "4.19.1", + "@algolia/logger-console": "4.19.1", + "@algolia/requester-browser-xhr": "4.19.1", + "@algolia/requester-common": "4.19.1", + "@algolia/requester-node-http": "4.19.1", + "@algolia/transporter": "4.19.1" } }, "node_modules/ansi-regex": { @@ -1252,9 +1251,9 @@ } }, "node_modules/ansi-sequence-parser": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/ansi-sequence-parser/-/ansi-sequence-parser-1.1.0.tgz", - "integrity": "sha512-lEm8mt52to2fT8GhciPCGeCXACSz2UwIN4X2e2LJSnZ5uAbn2/dsYdOmUXq0AtWS5cpAupysIneExOgH0Vd2TQ==", + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/ansi-sequence-parser/-/ansi-sequence-parser-1.1.1.tgz", + "integrity": "sha512-vJXt3yiaUL4UU546s3rPXlsry/RnM730G1+HkpKE012AN0sx1eOrxSu95oKDIonskeLTijMgqWZ3uDEe3NFvyg==", "dev": true }, "node_modules/ansi-styles": { @@ -1285,12 +1284,6 @@ "node": ">= 8" } }, - "node_modules/arg": { - "version": "5.0.2", - "resolved": "https://registry.npmjs.org/arg/-/arg-5.0.2.tgz", - "integrity": "sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg==", - "dev": true - }, "node_modules/argparse": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", @@ -1523,9 +1516,9 @@ } }, "node_modules/esbuild": { - "version": "0.17.19", - "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.17.19.tgz", - "integrity": "sha512-XQ0jAPFkK/u3LcVRcvVHQcTIqD6E2H1fvZMA5dQPSOWb3suUbWbfbRf94pjc0bNzRYLfIrDRQXr7X+LHIm5oHw==", + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.18.20.tgz", + "integrity": "sha512-ceqxoedUrcayh7Y7ZX6NdbbDzGROiyVBgC4PriJThBKSVPWnnFHZAkfI1lJT8QFkOwH4qOS2SJkS4wvpGl8BpA==", "dev": true, "hasInstallScript": true, "bin": { @@ -1535,28 +1528,28 @@ "node": ">=12" }, "optionalDependencies": { - "@esbuild/android-arm": "0.17.19", - "@esbuild/android-arm64": "0.17.19", - "@esbuild/android-x64": "0.17.19", - "@esbuild/darwin-arm64": "0.17.19", - "@esbuild/darwin-x64": "0.17.19", - "@esbuild/freebsd-arm64": "0.17.19", - "@esbuild/freebsd-x64": "0.17.19", - "@esbuild/linux-arm": "0.17.19", - "@esbuild/linux-arm64": "0.17.19", - "@esbuild/linux-ia32": "0.17.19", - "@esbuild/linux-loong64": "0.17.19", - "@esbuild/linux-mips64el": "0.17.19", - "@esbuild/linux-ppc64": "0.17.19", - "@esbuild/linux-riscv64": "0.17.19", - "@esbuild/linux-s390x": "0.17.19", - "@esbuild/linux-x64": "0.17.19", - "@esbuild/netbsd-x64": "0.17.19", - "@esbuild/openbsd-x64": "0.17.19", - "@esbuild/sunos-x64": "0.17.19", - "@esbuild/win32-arm64": "0.17.19", - "@esbuild/win32-ia32": "0.17.19", - "@esbuild/win32-x64": "0.17.19" + "@esbuild/android-arm": "0.18.20", + "@esbuild/android-arm64": "0.18.20", + "@esbuild/android-x64": "0.18.20", + "@esbuild/darwin-arm64": "0.18.20", + "@esbuild/darwin-x64": "0.18.20", + "@esbuild/freebsd-arm64": "0.18.20", + "@esbuild/freebsd-x64": "0.18.20", + "@esbuild/linux-arm": "0.18.20", + "@esbuild/linux-arm64": "0.18.20", + "@esbuild/linux-ia32": "0.18.20", + "@esbuild/linux-loong64": "0.18.20", + "@esbuild/linux-mips64el": "0.18.20", + "@esbuild/linux-ppc64": "0.18.20", + "@esbuild/linux-riscv64": "0.18.20", + "@esbuild/linux-s390x": "0.18.20", + "@esbuild/linux-x64": "0.18.20", + "@esbuild/netbsd-x64": "0.18.20", + "@esbuild/openbsd-x64": "0.18.20", + "@esbuild/sunos-x64": "0.18.20", + "@esbuild/win32-arm64": "0.18.20", + "@esbuild/win32-ia32": "0.18.20", + "@esbuild/win32-x64": "0.18.20" } }, "node_modules/escape-string-regexp": { @@ -1572,27 +1565,27 @@ } }, "node_modules/eslint": { - "version": "8.42.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.42.0.tgz", - "integrity": "sha512-ulg9Ms6E1WPf67PHaEY4/6E2tEn5/f7FXGzr3t9cBMugOmf1INYvuUwwh1aXQN4MfJ6a5K2iNwP3w4AColvI9A==", + "version": "8.46.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.46.0.tgz", + "integrity": "sha512-cIO74PvbW0qU8e0mIvk5IV3ToWdCq5FYG6gWPHHkx6gNdjlbAYvtfHmlCMXxjcoVaIdwy/IAt3+mDkZkfvb2Dg==", "dev": true, "dependencies": { "@eslint-community/eslint-utils": "^4.2.0", - "@eslint-community/regexpp": "^4.4.0", - "@eslint/eslintrc": "^2.0.3", - "@eslint/js": "8.42.0", + "@eslint-community/regexpp": "^4.6.1", + "@eslint/eslintrc": "^2.1.1", + "@eslint/js": "^8.46.0", "@humanwhocodes/config-array": "^0.11.10", "@humanwhocodes/module-importer": "^1.0.1", "@nodelib/fs.walk": "^1.2.8", - "ajv": "^6.10.0", + "ajv": "^6.12.4", "chalk": "^4.0.0", "cross-spawn": "^7.0.2", "debug": "^4.3.2", "doctrine": "^3.0.0", "escape-string-regexp": "^4.0.0", - "eslint-scope": "^7.2.0", - "eslint-visitor-keys": "^3.4.1", - "espree": "^9.5.2", + "eslint-scope": "^7.2.2", + "eslint-visitor-keys": "^3.4.2", + "espree": "^9.6.1", "esquery": "^1.4.2", "esutils": "^2.0.2", "fast-deep-equal": "^3.1.3", @@ -1602,7 +1595,6 @@ "globals": "^13.19.0", "graphemer": "^1.4.0", "ignore": "^5.2.0", - "import-fresh": "^3.0.0", "imurmurhash": "^0.1.4", "is-glob": "^4.0.0", "is-path-inside": "^3.0.3", @@ -1612,9 +1604,8 @@ "lodash.merge": "^4.6.2", "minimatch": "^3.1.2", "natural-compare": "^1.4.0", - "optionator": "^0.9.1", + "optionator": "^0.9.3", "strip-ansi": "^6.0.1", - "strip-json-comments": "^3.1.0", "text-table": "^0.2.0" }, "bin": { @@ -1628,17 +1619,17 @@ } }, "node_modules/eslint-plugin-vue": { - "version": "9.14.1", - "resolved": "https://registry.npmjs.org/eslint-plugin-vue/-/eslint-plugin-vue-9.14.1.tgz", - "integrity": "sha512-LQazDB1qkNEKejLe/b5a9VfEbtbczcOaui5lQ4Qw0tbRBbQYREyxxOV5BQgNDTqGPs9pxqiEpbMi9ywuIaF7vw==", + "version": "9.17.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-vue/-/eslint-plugin-vue-9.17.0.tgz", + "integrity": "sha512-r7Bp79pxQk9I5XDP0k2dpUC7Ots3OSWgvGZNu3BxmKK6Zg7NgVtcOB6OCna5Kb9oQwJPl5hq183WD0SY5tZtIQ==", "dev": true, "dependencies": { - "@eslint-community/eslint-utils": "^4.3.0", + "@eslint-community/eslint-utils": "^4.4.0", "natural-compare": "^1.4.0", - "nth-check": "^2.0.1", - "postcss-selector-parser": "^6.0.9", - "semver": "^7.3.5", - "vue-eslint-parser": "^9.3.0", + "nth-check": "^2.1.1", + "postcss-selector-parser": "^6.0.13", + "semver": "^7.5.4", + "vue-eslint-parser": "^9.3.1", "xml-name-validator": "^4.0.0" }, "engines": { @@ -1649,9 +1640,9 @@ } }, "node_modules/eslint-scope": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.2.0.tgz", - "integrity": "sha512-DYj5deGlHBfMt15J7rdtyKNq/Nqlv5KfU4iodrQ019XESsRnwXH9KAE0y3cwtUHDo2ob7CypAnCqefh6vioWRw==", + "version": "7.2.2", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.2.2.tgz", + "integrity": "sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg==", "dev": true, "dependencies": { "esrecurse": "^4.3.0", @@ -1665,9 +1656,9 @@ } }, "node_modules/eslint-visitor-keys": { - "version": "3.4.1", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.1.tgz", - "integrity": "sha512-pZnmmLwYzf+kWaM/Qgrvpen51upAktaaiI01nsJD/Yr3lMOdNtq0cxkrrg16w64VtisN6okbs7Q8AfGqj4c9fA==", + "version": "3.4.2", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.2.tgz", + "integrity": "sha512-8drBzUEyZ2llkpCA67iYrgEssKDUu68V8ChqqOfFupIaG/LCVPUT+CoGJpT77zJprs4T/W7p07LP7zAIMuweVw==", "dev": true, "engines": { "node": "^12.22.0 || ^14.17.0 || >=16.0.0" @@ -1677,12 +1668,12 @@ } }, "node_modules/espree": { - "version": "9.5.2", - "resolved": "https://registry.npmjs.org/espree/-/espree-9.5.2.tgz", - "integrity": "sha512-7OASN1Wma5fum5SrNhFMAMJxOUAbhyfQ8dQ//PJaJbNw0URTPWqIghHWt1MmAANKhHZIYOHruW4Kw4ruUWOdGw==", + "version": "9.6.1", + "resolved": "https://registry.npmjs.org/espree/-/espree-9.6.1.tgz", + "integrity": "sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ==", "dev": true, "dependencies": { - "acorn": "^8.8.0", + "acorn": "^8.9.0", "acorn-jsx": "^5.3.2", "eslint-visitor-keys": "^3.4.1" }, @@ -1773,9 +1764,9 @@ "dev": true }, "node_modules/fast-glob": { - "version": "3.2.12", - "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.2.12.tgz", - "integrity": "sha512-DVj4CQIYYow0BlaelwK1pHl5n5cRSJfM60UA0zK891sVInoPri2Ekj7+e1CT3/3qxXenpI+nBBmQAcJPJgaj4w==", + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.1.tgz", + "integrity": "sha512-kNFPyjhh5cKjrUltxs+wFx+ZkbRaxxmZ+X0ZU31SOsxCEtP9VPgtq2teZw1DebupL5GmDaNQ6yKMMVcM41iqDg==", "dev": true, "dependencies": { "@nodelib/fs.stat": "^2.0.2", @@ -1881,12 +1872,12 @@ "dev": true }, "node_modules/focus-trap": { - "version": "7.4.3", - "resolved": "https://registry.npmjs.org/focus-trap/-/focus-trap-7.4.3.tgz", - "integrity": "sha512-BgSSbK4GPnS2VbtZ50VtOv1Sti6DIkj3+LkVjiWMNjLeAp1SH1UlLx3ULu/DCu4vq5R4/uvTm+zrvsMsuYmGLg==", + "version": "7.5.2", + "resolved": "https://registry.npmjs.org/focus-trap/-/focus-trap-7.5.2.tgz", + "integrity": "sha512-p6vGNNWLDGwJCiEjkSK6oERj/hEyI9ITsSwIUICBoKLlWiTWXJRfQibCwcoi50rTZdbi87qDtUlMCmQwsGSgPw==", "dev": true, "dependencies": { - "tabbable": "^6.1.2" + "tabbable": "^6.2.0" } }, "node_modules/fs.realpath": { @@ -2038,9 +2029,9 @@ } }, "node_modules/immutable": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/immutable/-/immutable-4.3.0.tgz", - "integrity": "sha512-0AOCmOip+xgJwEVTQj1EfiDDOkPmuyllDuTuEX+DDXUgapLAsBIfkg3sxCYyCEA8mQqZrrxPUGjcOQ2JS3WLkg==", + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/immutable/-/immutable-4.3.2.tgz", + "integrity": "sha512-oGXzbEDem9OOpDWZu88jGiYCvIsLHMvGw+8OXlpsvTFvIQplQbjg1B1cvKg8f7Hoch6+NGjpPsH1Fr+Mc2D1aA==", "dev": true }, "node_modules/import-fresh": { @@ -2242,12 +2233,12 @@ } }, "node_modules/magic-string": { - "version": "0.30.0", - "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.0.tgz", - "integrity": "sha512-LA+31JYDJLs82r2ScLrlz1GjSgu66ZV518eyWT+S8VhyQn/JL0u9MeBOvQMGYiPk1DBiSN9DDMOcXvigJZaViQ==", + "version": "0.30.2", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.2.tgz", + "integrity": "sha512-lNZdu7pewtq/ZvWUp9Wpf/x7WzMTsR26TWV03BRZrXFsv+BI6dy8RAiKgm1uM/kyR0rCfUcqvOlXKG66KhIGug==", "dev": true, "dependencies": { - "@jridgewell/sourcemap-codec": "^1.4.13" + "@jridgewell/sourcemap-codec": "^1.4.15" }, "engines": { "node": ">=12" @@ -2360,17 +2351,17 @@ } }, "node_modules/optionator": { - "version": "0.9.1", - "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.1.tgz", - "integrity": "sha512-74RlY5FCnhq4jRxVUPKDaRwrVNXMqsGsiW6AJw4XK8hmtm10wC0ypZBLw5IIp85NZMr91+qd1RvvENwg7jjRFw==", + "version": "0.9.3", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.3.tgz", + "integrity": "sha512-JjCoypp+jKn1ttEFExxhetCKeJt9zhAgAve5FXHixTvFDW/5aEktX9bufBKLRRMdU7bNtpLfcGu94B3cdEJgjg==", "dev": true, "dependencies": { + "@aashutoshrathi/word-wrap": "^1.2.3", "deep-is": "^0.1.3", "fast-levenshtein": "^2.0.6", "levn": "^0.4.1", "prelude-ls": "^1.2.1", - "type-check": "^0.4.0", - "word-wrap": "^1.2.3" + "type-check": "^0.4.0" }, "engines": { "node": ">= 0.8.0" @@ -2473,9 +2464,9 @@ } }, "node_modules/postcss": { - "version": "8.4.24", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.24.tgz", - "integrity": "sha512-M0RzbcI0sO/XJNucsGjvWU9ERWxb/ytp1w6dKtxTKgixdtQDq4rmx/g8W1hnaheq9jgwL/oyEdH5Bc4WwJKMqg==", + "version": "8.4.27", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.27.tgz", + "integrity": "sha512-gY/ACJtJPSmUFPDCHtX78+01fHa64FaU4zaaWfuh1MhGJISufJAH4cun6k/8fwsHYeK4UQmENQK+tRLCFJE8JQ==", "dev": true, "funding": [ { @@ -2514,9 +2505,9 @@ } }, "node_modules/preact": { - "version": "10.15.1", - "resolved": "https://registry.npmjs.org/preact/-/preact-10.15.1.tgz", - "integrity": "sha512-qs2ansoQEwzNiV5eAcRT1p1EC/dmEzaATVDJNiB3g2sRDWdA7b7MurXdJjB2+/WQktGWZwxvDrnuRFbWuIr64g==", + "version": "10.16.0", + "resolved": "https://registry.npmjs.org/preact/-/preact-10.16.0.tgz", + "integrity": "sha512-XTSj3dJ4roKIC93pald6rWuB2qQJO9gO2iLLyTe87MrjQN+HklueLsmskbywEWqCHlclgz3/M4YLL2iBr9UmMA==", "dev": true, "funding": { "type": "opencollective", @@ -2608,9 +2599,9 @@ } }, "node_modules/rollup": { - "version": "3.25.1", - "resolved": "https://registry.npmjs.org/rollup/-/rollup-3.25.1.tgz", - "integrity": "sha512-tywOR+rwIt5m2ZAWSe5AIJcTat8vGlnPFAv15ycCrw33t6iFsXZ6mzHVFh2psSjxQPmI+xgzMZZizUAukBI4aQ==", + "version": "3.28.0", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-3.28.0.tgz", + "integrity": "sha512-d7zhvo1OUY2SXSM6pfNjgD5+d0Nz87CUp4mt8l/GgVP3oBsPwzNvSzyu1me6BSG9JIgWNTVcafIXBIyM8yQ3yw==", "dev": true, "bin": { "rollup": "dist/bin/rollup" @@ -2647,9 +2638,9 @@ } }, "node_modules/sass": { - "version": "1.63.3", - "resolved": "https://registry.npmjs.org/sass/-/sass-1.63.3.tgz", - "integrity": "sha512-ySdXN+DVpfwq49jG1+hmtDslYqpS7SkOR5GpF6o2bmb1RL/xS+wvPmegMvMywyfsmAV6p7TgwXYGrCZIFFbAHg==", + "version": "1.65.1", + "resolved": "https://registry.npmjs.org/sass/-/sass-1.65.1.tgz", + "integrity": "sha512-9DINwtHmA41SEd36eVPQ9BJKpn7eKDQmUHmpI0y5Zv2Rcorrh0zS+cFrt050hdNbmmCNKTW3hV5mWfuegNRsEA==", "dev": true, "dependencies": { "chokidar": ">=3.0.0 <4.0.0", @@ -2663,16 +2654,10 @@ "node": ">=14.0.0" } }, - "node_modules/sax": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/sax/-/sax-1.2.4.tgz", - "integrity": "sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw==", - "dev": true - }, "node_modules/search-insights": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/search-insights/-/search-insights-2.6.0.tgz", - "integrity": "sha512-vU2/fJ+h/Mkm/DJOe+EaM5cafJv/1rRTZpGJTuFPf/Q5LjzgMDsqPdSaZsAe+GAWHHsfsu+rQSAn6c8IGtBEVw==", + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/search-insights/-/search-insights-2.7.0.tgz", + "integrity": "sha512-GLbVaGgzYEKMvuJbHRhLi1qoBFnjXZGZ6l4LxOYPCp4lI2jDRB3jPU9/XNhMwv6kvnA9slTreq6pvK+b3o3aqg==", "dev": true, "peer": true, "engines": { @@ -2693,9 +2678,9 @@ } }, "node_modules/semver": { - "version": "7.5.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.1.tgz", - "integrity": "sha512-Wvss5ivl8TMRZXXESstBA4uR5iXgEN/VC5/sOcuXdVLzcdkz4HWetIoRfG5gb5X+ij/G9rw9YoGn3QoQ8OCSpw==", + "version": "7.5.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", + "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", "dev": true, "dependencies": { "lru-cache": "^6.0.0" @@ -2729,9 +2714,9 @@ } }, "node_modules/shiki": { - "version": "0.14.2", - "resolved": "https://registry.npmjs.org/shiki/-/shiki-0.14.2.tgz", - "integrity": "sha512-ltSZlSLOuSY0M0Y75KA+ieRaZ0Trf5Wl3gutE7jzLuIcWxLp5i/uEnLoQWNvgKXQ5OMpGkJnVMRLAuzjc0LJ2A==", + "version": "0.14.3", + "resolved": "https://registry.npmjs.org/shiki/-/shiki-0.14.3.tgz", + "integrity": "sha512-U3S/a+b0KS+UkTyMjoNojvTgrBHjgp7L6ovhFVZsXmBGnVdQ4K4U9oK0z63w538S91ATngv1vXigHCSWOwnr+g==", "dev": true, "dependencies": { "ansi-sequence-parser": "^1.1.0", @@ -2740,31 +2725,6 @@ "vscode-textmate": "^8.0.0" } }, - "node_modules/sitemap": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/sitemap/-/sitemap-7.1.1.tgz", - "integrity": "sha512-mK3aFtjz4VdJN0igpIJrinf3EO8U8mxOPsTBzSsy06UtjZQJ3YY3o3Xa7zSc5nMqcMrRwlChHZ18Kxg0caiPBg==", - "dev": true, - "dependencies": { - "@types/node": "^17.0.5", - "@types/sax": "^1.2.1", - "arg": "^5.0.0", - "sax": "^1.2.4" - }, - "bin": { - "sitemap": "dist/cli.js" - }, - "engines": { - "node": ">=12.0.0", - "npm": ">=5.6.0" - } - }, - "node_modules/sitemap/node_modules/@types/node": { - "version": "17.0.45", - "resolved": "https://registry.npmjs.org/@types/node/-/node-17.0.45.tgz", - "integrity": "sha512-w+tIMs3rq2afQdsPJlODhoUEKzFP1ayaoyl1CcnwtIlsVe7K7bA1NGm4s3PraqTLlXnbIN84zuBlxBWo1u9BLw==", - "dev": true - }, "node_modules/slash": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", @@ -2835,9 +2795,9 @@ } }, "node_modules/tabbable": { - "version": "6.1.2", - "resolved": "https://registry.npmjs.org/tabbable/-/tabbable-6.1.2.tgz", - "integrity": "sha512-qCN98uP7i9z0fIS4amQ5zbGBOq+OSigYeGvPy7NDk8Y9yncqDZ9pRPgfsc2PJIVM9RrJj7GIfuRgmjoUU9zTHQ==", + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/tabbable/-/tabbable-6.2.0.tgz", + "integrity": "sha512-Cat63mxsVJlzYvN51JmVXIgNoUokrIaT2zLclCXjRd8boZ0004U4KCs/sToJ75C6sdlByWxpYnb5Boif1VSFew==", "dev": true }, "node_modules/text-table": { @@ -2904,9 +2864,9 @@ } }, "node_modules/typescript": { - "version": "5.1.3", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.1.3.tgz", - "integrity": "sha512-XH627E9vkeqhlZFQuL+UsyAXEnibT0kWR2FWONlr4sTjvxyJYnyefgrkyECLzM5NenmKzRAy2rR/OlYLA1HkZw==", + "version": "5.1.6", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.1.6.tgz", + "integrity": "sha512-zaWCozRZ6DLEWAWFrVDz1H6FVXzUSfTy5FUMWsQlU8Ym5JP9eO4xkTIROFCQvhQf61z6O/G6ugw3SgAnvvm+HA==", "dev": true, "peer": true, "bin": { @@ -2933,14 +2893,14 @@ "dev": true }, "node_modules/vite": { - "version": "4.3.9", - "resolved": "https://registry.npmjs.org/vite/-/vite-4.3.9.tgz", - "integrity": "sha512-qsTNZjO9NoJNW7KnOrgYwczm0WctJ8m/yqYAMAK9Lxt4SoySUfS5S8ia9K7JHpa3KEeMfyF8LoJ3c5NeBJy6pg==", + "version": "4.4.9", + "resolved": "https://registry.npmjs.org/vite/-/vite-4.4.9.tgz", + "integrity": "sha512-2mbUn2LlUmNASWwSCNSJ/EG2HuSRTnVNaydp6vMCm5VIqJsjMfbIWtbH2kDuwUVW5mMUKKZvGPX/rqeqVvv1XA==", "dev": true, "dependencies": { - "esbuild": "^0.17.5", - "postcss": "^8.4.23", - "rollup": "^3.21.0" + "esbuild": "^0.18.10", + "postcss": "^8.4.27", + "rollup": "^3.27.1" }, "bin": { "vite": "bin/vite.js" @@ -2948,12 +2908,16 @@ "engines": { "node": "^14.18.0 || >=16.0.0" }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, "optionalDependencies": { "fsevents": "~2.3.2" }, "peerDependencies": { "@types/node": ">= 14", "less": "*", + "lightningcss": "^1.21.0", "sass": "*", "stylus": "*", "sugarss": "*", @@ -2966,6 +2930,9 @@ "less": { "optional": true }, + "lightningcss": { + "optional": true + }, "sass": { "optional": true }, @@ -2981,23 +2948,23 @@ } }, "node_modules/vitepress": { - "version": "1.0.0-beta.2", - "resolved": "https://registry.npmjs.org/vitepress/-/vitepress-1.0.0-beta.2.tgz", - "integrity": "sha512-DBXYjtYbm3W1IPPJ2TiCaK/XK+o/2XmL2+jslOGKm+txcbmG0kbeB+vadC5tCUZA9NdA+9Ywj3M4548c7t/SDg==", + "version": "1.0.0-rc.4", + "resolved": "https://registry.npmjs.org/vitepress/-/vitepress-1.0.0-rc.4.tgz", + "integrity": "sha512-JCQ89Bm6ECUTnyzyas3JENo00UDJeK8q1SUQyJYou+4Yz5BKEc/F3O21cu++DnUT2zXc0kvQ2Aj4BZCc/nioXQ==", "dev": true, "dependencies": { - "@docsearch/css": "^3.5.0", - "@docsearch/js": "^3.5.0", + "@docsearch/css": "^3.5.1", + "@docsearch/js": "^3.5.1", "@vitejs/plugin-vue": "^4.2.3", "@vue/devtools-api": "^6.5.0", - "@vueuse/core": "^10.1.2", - "@vueuse/integrations": "^10.1.2", + "@vueuse/core": "^10.3.0", + "@vueuse/integrations": "^10.3.0", "body-scroll-lock": "4.0.0-beta.0", - "focus-trap": "^7.4.3", + "focus-trap": "^7.5.2", "mark.js": "8.11.1", "minisearch": "^6.1.0", - "shiki": "^0.14.2", - "vite": "^4.3.9", + "shiki": "^0.14.3", + "vite": "^4.4.9", "vue": "^3.3.4" }, "bin": { @@ -3068,15 +3035,6 @@ "node": ">= 8" } }, - "node_modules/word-wrap": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.3.tgz", - "integrity": "sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/wrappy": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", diff --git a/package.json b/package.json index f864c79d1..f94c5060b 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { - "name": "cap-docs", - "version": "0.2.0", + "name": "@cap-js/docs", + "version": "0.8.0", "description": "Capire on VitePress", "type": "module", "scripts": { @@ -24,7 +24,6 @@ "eslint-plugin-vue": "^9.11.0", "gray-matter": "^4.0.3", "sass": "^1.62.1", - "sitemap": "^7.1.1", - "vitepress": "^1.0.0-beta.2" + "vitepress": "^1.0.0-rc.4" } } diff --git a/project-words.txt b/project-words.txt new file mode 100644 index 000000000..e588a3393 --- /dev/null +++ b/project-words.txt @@ -0,0 +1,5 @@ +aggregatable +unlocalized +undiscloses +isdir +Undeploying diff --git a/public/assets/logos/apple-touch-icon.png b/public/assets/logos/apple-touch-icon.png new file mode 100644 index 000000000..ab0be875c Binary files /dev/null and b/public/assets/logos/apple-touch-icon.png differ diff --git a/public/assets/logos/favicon.ico b/public/assets/logos/favicon.ico new file mode 100644 index 000000000..385bcabad Binary files /dev/null and b/public/assets/logos/favicon.ico differ diff --git a/tools/cds-typer.md b/tools/cds-typer.md new file mode 100644 index 000000000..65e8d03e3 --- /dev/null +++ b/tools/cds-typer.md @@ -0,0 +1,441 @@ +--- +label: cds-typer +synopsis: > + This page explains the package cds-typer in depth. +# layout: node-js +status: released +--- + +# CDS Typer {#cds-typer} + +The following chapter describes the [`cds-typer` package](https://www.npmjs.com/package/@cap-js/cds-typer) in detail using the [bookshop sample](https://github.com/SAP-samples/cloud-cap-samples/tree/main/bookshop) as a running example. + +## Quickstart using VS Code {#cds-typer-vscode} +1. Make sure you have the [SAP CDS Language Support extension for VSCode](https://marketplace.visualstudio.com/items?itemName=SAPSE.vscode-cds) installed. +2. In your project's root, execute `cds add typer`. +3. Install the newly added dev-dependency using `npm i`. +4. Saving any _.cds_ file of your model from VSCode triggers the type generation process. +5. Model types now have to be imported to service implementation files by traditional imports of the generated files: + +```js +// without cds-typer +const { Books } = cds.entities(…) +service.before('CREATE' Books, ({ data }) => { /* data is of type any */}) + +// ✨ with cds-typer +const { Books } = require('#cds-models/…') +service.before('CREATE' Books, ({ data }) => { /* data is of type Books */}) +``` + + +::: details How it works: + +The extension will automatically trigger the type generator whenever you hit _save_ on a _.cds_ file that is part of your model. That ensures that the generated type information stays in sync with your model. If you stick to the defaults, saving a _.cds_ file will have the type generator emit [its type files](#emitted-type-files) into the directory _@cds-models_ in your project's root. + +Opening your VSCode settings and typing "`cds type generator`" into the search bar will reveal several options to configure the type generation process. Output, warnings, and error messages of the process can be found in the output window called "`CDS`". + +::: + +[Learn more about the `typer` facet.](#typer-facet){.learn-more} +[Learn about other options to use `cds-typer`.](#usage-options){.learn-more} + +## Using Emitted Types in Your Service +The types emitted by the type generator are tightly integrated with the CDS API. The following section illustrates where the generated types are recognized by CDS. + +### CQL + +Most CQL constructs have an overloaded signature to support passing in generated types. Chained calls will offer code completion related to the type you pass in. + +```js +// how you would have done it before (and can still do it) +SELECT('Books') // etc... + +// how you can do it using generated types +const { Book, Books } = require('#cds-models/sap/capire/Bookshop') + +// SELECT +SELECT(Books) +SELECT.one(Book) +SELECT(Books, b => { b.ID }) // projection +SELECT(Books, b => { b.author(a => a.ID.as('author_id')) }) // nested projection + +// INSERT / UPSERT +INSERT.into(Books, […]) +INSERT.into(Books).columns(['title', 'ID']) // column names derived from Books' properties + +// DELETE +DELETE(Books).byKey(42) +``` + +Note that your entities will expose additional capabilities in the context of CQL, such as the `.as(…)` method to specify an alias. + +### CRUD Handlers +The CRUD handlers `before`, `on`, and `after` accept generated types: + +```js +// the paylod is known to contain Books inside the respective handlers +service.before('READ', Books, req => { … } +service.on('READ', Books, req => { … } +service.after('READ', Books, req => { … } +``` + +### Actions + +In the same manner, actions can be combined with `on`: + +```js +const { submitOrder } = require('#cds-models/sap/capire/Bookshop') + +service.on(submitOrder, (…) => { /* implementation of 'submitOrder' */ }) +``` + +::: warning _Lambda Functions vs. Fully Fledged Functions_ + +Using anything but lambda functions for either CRUD handler or action implementation will make it impossible for the LSP to infer the parameter types. + +You can remedy this by specifying the expected type yourself via [JSDoc](https://jsdoc.app/): + +```js +service.on('READ', Books, readBooksHandler) + +/** @param {{ data: import('#cds-models/sap/capire/Bookshop').Books }} req */ +function readBooksHandler (req) { + // req.data is now properly known to be of type Books again +} +``` + +::: + + +### Enums + +CDS enums are supported by `cds-typer` and are represented during runtime as well. So you can assign values to enum-typed properties with more confidence: + +```cds +type Priority: String enum { + LOW = 'Low'; + MEDIUM = 'Medium'; + HIGH = 'High'; +} + +entity Tickets { + priority: Priority; + … +} +``` + +```js +const { Ticket, Priority } = require('…') + +service.before('CREATE', Ticket, (req) => { + req.data.priority = Priority.LOW // [!code focus] + // / \ // [!code focus] + // inferred type: Priority suggests LOW, MEDIUM, HIGH // [!code focus] +}) + +``` + +### Handling Optional Properties +Per default, all properties of emitted types are set to be optional. This reflects how entities can be partial in handlers. + +CDS file: + +```cds +entity Author { + name: String; // [!code focus] + … +} + +entity Book { + author: Association to Author; // [!code focus] + … +} +``` + +Generated type file: + +```ts +class Author { + name?: string // [!code focus] + … +} + +class Book { + author?: Association.to // [!code focus] + … +} +``` + +In consequence, you will get called out by the type system when trying to chain property calls. You can overcome this in a variety of ways: + +```ts +const myBook: Book = … + +// (i) optional chaining +const authorName = myBook.author?.name + +// (ii) explicitly ruling out the undefined type +if (myBook.author !== undefined) { + const authorName = myBook.author.name +} + +// (iii) non-null assertion operator +const authorName = myBook.author!.name + +// (iv) explicitly casting your object to a type where all properties are attached +const myAttachedBook = myBook as Required +const authorName = myAttachedBook.author.name + +// (v) explicitly casting your object to a type where the required property is attached +const myPartiallyAttachedBook = myBook as Book & { author: Author } +const authorName = myPartiallyAttachedBook.author.name +``` + +Note that (iii) through (v) are specific to TypeScript, while (i) and (ii) can also be used in JavaScript projects. + +## Fine Tuning +### Singular/ Plural +The generated types offer both a singular and plural form for convenience. The derivation of these names uses a heuristic that assumes entities are named with an English noun in plural form, following the [best practice guide](https://cap.cloud.sap/docs/guides/domain-modeling#pluralize-entity-names). + +Naturally, this best practice can't be enforced on every model. Even for names that do follow best practices, the heuristic can fail. If you find that you would like to specify custom identifiers for singular or plural forms, you can do so using the `@singular` or `@plural` annotations. + +CDS file: +```cds +// model.cds +@singular: 'Mouse' +entity Mice { … } + +@plural: 'FlockOfSheep' +entity Sheep { … } +``` + +Generated type file: + +```ts +// index.ts +export class Mouse { … } +export class Mice { … } +export class Sheep { … } +export class FlockOfSheep { … } +``` + +### Strict Property Checks in JavaScript Projects +You can enable strict property checking for your JavaScript project by adding the [`checkJs: true`](https://www.typescriptlang.org/tsconfig#checkJs) setting to your _jsconfig.json_ or _tsconfig.json_. +This will consider referencing properties in generated types that are not explicitly defined as error. + +## Usage Options + +Besides using the [SAP CDS Language Support extension for VSCode](https://marketplace.visualstudio.com/items?itemName=SAPSE.vscode-cds), you have the option to use `cds-typer` on the command line and programmatically. + +### Command Line Interface (CLI) {#typer-cli} + +```sh +npx @cap-js/cds-typer /home/mybookshop/db/schema.cds --outputDirectory /home/mybookshop +``` + +The CLI offers several parameters which you can list using the `--help` parameter. + +::: details You should then see the following output: + + +```log + +> @cap-js/cds-typer@0.4.0 cli +> node lib/cli.js --help + +SYNOPSIS + + cds-typer [cds file | "*"] + + Generates type information based on a CDS model. + Call with at least one positional parameter pointing + to the (root) CDS file you want to compile. + +OPTIONS + + --help + + This text. + + --inlineDeclarations: + (default: structured) + + Whether to resolve inline type declarations + flat: (x_a, x_b, ...) + or structured: (x: {a, b}). + + --jsConfigPath: + + Path to where the jsconfig.json should be written. + If specified, cds-typer will create a jsconfig.json file and + set it up to restrict property usage in types entities to + existing properties only. + + --logLevel: + (default: NONE) + + Minimum log level that is printed. + + --outputDirectory: + (default: ./) + + Root directory to write the generated files to. + + --propertiesOptional: + (default: true) + + If set to true, properties in entities are + always generated as optional (a?: T). + + --version + + Prints the version of this tool. +``` +::: + +### Programmatically + +`cds-typer` can also be used programmatically in your Node.js app to consume CSN from either an in-memory structure (`compileFromCSN(…)`) or from _.cds_ files (`compileFromFile(…)`). Refer to the [source code](https://github.com/cap-js/cds-typer/blob/main/lib/compile.js) for more information on the API. + +::: warning Could alter CSN! + +Applying `cds-typer` to an in-memory CSN structure may be impure, meaning that it could alter the CSN. If you use the type generator this way, you may want to apply it as last step of your tool chain. + +::: + +## Integrate Into TypeScript Projects +The types emitted by `cds-typer` can be used in TypeScript projects as well! Depending on your project setup you may have to do some manual configuration. + +1. Make sure the directory the types are generated into are part of your project's files. You will either have to add that folder to your `rootDirs` in your _tsconfig.json_ or make sure the types are generated into a directory that is already part of your `rootDir`. +2. Preferably run the project using `cds-ts`. +3. If you have to use `tsc`, for example for deployment, you have to touch up on the generated files. Assume your types are in _@cds-models_ below your project's root directory and your code is transpiled to _dist/_, you would use: + +```sh +tsc && cp -r @cds-models dist +``` + +## Integrate Into Your CI +As the generated types are build artifacts, we recommend to exclude them from your software versioning process. Still, as using `cds-typer` changes how you include your model in your service implementation, you need to include the emitted files when releasing your project or running tests in your continuous integration pipeline. +You should therefore trigger `cds-typer` as part of your build process. One easy way to do so is to add a variation of the following command to your build script: + +```sh +npx @cap-js/cds-typer "*" --outputDirectory @cds-models +``` +Make sure to add the quotes around the asterisk so your shell environment does not expand the pattern. + + +## About The Facet {#typer-facet} +Type generation can be added to your project as [facet](../tools/#cds-init-add) via `cds add typer`. + +::: details Under the hood + +Adding this facet effectively does four things: + +1. Adds `@cap-js/cds-typer` as a dev-dependency (⚠️ which you still have to install using `npm i`) +2. Creates (or modifies) a _jsconfig.json_ file to support intellisense for the generated types +3. Modifies _package.json_ to enable [subpath imports](https://nodejs.org/api/packages.html#subpath-imports) for the generated types +4. Adds `@cds-models` (the default output folder for generated files) to your project's _.gitignore_ +::: + +::: warning _TypeScript Projects_ + +Adding the facet in a TypeScript project will adjust your _tsconfig.json_ instead. Note that you may have to manually add the type generator's configured output directory to the `rootDirs` entry in your +_tsconfig.json_, as we do not want to interfere with your configuration. + +::: + + +## About the Emitted Type Files {#emitted-type-files} + +The emitted types are bundled into a directory which contains a nested directory structure that mimics the namespaces of your CDS model. For the sake of brevity, we will assume them to be in a directory called _@cds-models_ in your project's root in the following sections. +For example, the sample model contains a namespace `sap.capire.bookshop`. You will therefore find the following file structure after the type generation has finished: + +```txt +@cds-models +└───sap + └───capire + └───bookshop + index.js + index.ts +``` + +Each _index.ts_ file will contain type information for one namespace. For each entity belonging to that namespace, you will find two exports, a singular and a plural form: + +```ts +// @cds-models/sap/capire/bookshop/index.ts +export class Author { … } +export class Authors { … } +export class Book { … } +export class Books { … } +``` + +The singular forms represent the entities from the original model and try to adhere to best practices of object oriented programming for naming classes in singular form. +The plural form exists as a convenience to refer to a collection of multiple entities. You can [fine tune](#fine-tuning) both singular and plural names that are used here. + +You could import these types by using absolute paths, but there is a more convenient way for doing so which will be described in the next section. + +## Subpath Imports +Adding type support via `cds add typer` includes adding [subpath imports](https://nodejs.org/api/packages.html#subpath-imports). Per default, the facet adds a mapping of `#cds-models/` to the default path your model's types are assumed to be generated to (_\/@cds-models/_). If you are generating your types to another path and want to use subpath imports, you will have to adjust this setting in your _package.json_ **and** _jsconfig.json_/ _tsconfig.json_ accordingly. + +Consider [the bookshop sample](https://github.com/SAP-samples/cloud-cap-samples/tree/main/bookshop) with the following structure with types already generated into _@cds-models_: + +```txt +bookstore +│ package.json +│ +└───@cds-models +│ └─── +│ +└───db +│ schema.cds +│ … +│ +└───srv +│ cat-service.cds +│ cat-service.js +│ … +│ +└─── … +``` + +The following two (equally valid) statements would amount to the same import [from within the catalog service](https://github.com/SAP-samples/cloud-cap-samples/blob/main/bookshop/srv/cat-service.js): + +```js +// srv/cat-service.js +const { Books } = require('../@cds-models/sap/capire/bookshop') +const { Books } = require('#cds-models/sap/capire/bookshop') +``` + +These imports will behave like [`cds.entities('sap.capire.bookshop')`](https://pages.github.tools.sap/cap/docs/node.js/cds-reflect#entities) during runtime, but offer you code completion and type hinting at design time: + +```js +class CatalogService extends cds.ApplicationService { init(){ + const { Book } = require('#cds-models/sap/capire/bookshop') + + this.on ('UPDATE', Book, req => { + // in here, req is known to hold a payload of type Book. + // Code completion therefore offers all the properties that are defined in the model. + }) +}) +``` + +Just as with `cds.entities(…)`, these imports can't be static, but need to be dynamic: + +```js +// ❌ works during design time, but will cause runtime errors +const { Book } = require('#cds-models/sap/capire/bookshop') + +class CatalogService extends cds.ApplicationService { init(){ + // ✅ works both at design time and at runtime + const { Book } = require('#cds-models/sap/capire/bookshop') +}) +``` diff --git a/tools/index.md b/tools/index.md index bcb26f557..2fded6415 100644 --- a/tools/index.md +++ b/tools/index.md @@ -258,7 +258,7 @@ The cell inputs/outputs are especially useful at later points in time when the p * To see which features are available in a CAP Notebook, open our [CAP Notebook page](#cap-notebooks-page): F1 → *CDS: Open CAP Notebooks Page* -* Magics, or magic commands, known from [IPython](https://ipython.readthedocs.io/en/stable/interactive/magics.html) are conventient functions to solve common problems. To see which line- and cell-magics can be used within a CAP Notebook, run a code cell with `%quickref`. +* Magics, or magic commands, known from [IPython](https://ipython.readthedocs.io/en/stable/interactive/magics.html) are convenient functions to solve common problems. To see which line- and cell-magics can be used within a CAP Notebook, run a code cell with `%quickref`. * Start an empty CAP Notebook by creating a _*.capnb_ file. @@ -665,7 +665,6 @@ The **CDS Lint** rules are a set of generic rules based on CAP best practices. T ### Customization -
#### Configuring CDS Lint Rules diff --git a/tsconfig.json b/tsconfig.json index 7f648c79b..3efbeffa6 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -13,7 +13,8 @@ "isolatedModules": true, "allowJs": true, "noEmit": true, - "allowImportingTsExtensions": true + "allowImportingTsExtensions": true, + "types": ["node", "vite/client"] }, "include": [ ".vitePress/**/*.ts",