From b84055f1a377176e4df7e31c6a9052fb229be098 Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 27 Sep 2026 19:41:03 +0000 Subject: [PATCH] Take the quickstart from an empty folder to a data app - Prerequisite checks: node -v (22+), cds version (cds-dk 10.x) - Optional sanity check of the tiny-sample project before the plugin - How to create srv/apps/hello.js in VS Code or a terminal - Replace the "Reading your own data" fragment with a complete BOOKS app that runs as-is in the tiny-sample project (search "Raven" leaves 1 row) - Tip: a redirect under app/ lists the app on CAP's index page, after a restart - Short "What is in the database" section, with the depth on the persistence page: where tables and rows come from, cds compile, cds repl, and a file-backed SQLite that survives restarts - Troubleshooting table in the quickstart; setup problems (cds not found, PowerShell execution policy, EACCES, port in use, login) and the restart case of NO_DRAFT_ENTRY in guide/troubleshooting - Next steps point at reference/deployment All steps measured with cds-dk 10.1.0, @sap/cds 10.1.1 and cap2ui5 0.1.0. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01Y47xEWed2qwpona5WWpt4B --- docs/guide/getting-started.md | 151 ++++++++++++++++++++++++++++++---- docs/guide/persistence.md | 87 ++++++++++++++++++++ docs/guide/troubleshooting.md | 51 ++++++++++++ 3 files changed, 274 insertions(+), 15 deletions(-) diff --git a/docs/guide/getting-started.md b/docs/guide/getting-started.md index ecbc817..cfdbce2 100644 --- a/docs/guide/getting-started.md +++ b/docs/guide/getting-started.md @@ -6,13 +6,21 @@ already have, or to a brand new one. There is no cap2UI5 project to clone. ## Prerequisites -- **Node.js ≥ 22** — `@abap2ui5/node-runtime`, which the plugin depends on, - requires it (`cap2ui5` itself says ≥ 20) +- **Node.js 22 or later** — `@abap2ui5/node-runtime`, which the plugin depends + on, requires it (`cap2ui5` itself says ≥ 20) - **`@sap/cds-dk`** installed globally, for the `cds` command -- Internet access — the page loads SAPUI5 from the SAP CDN +- Internet access — the page loads UI5 from the SAP CDN No database setup: CAP starts SQLite for you. +Check Node before anything else: + +```bash +node -v +``` + +It must print `v22` or higher. + ## 1. A CAP project and the plugin Skip `cds init` if you already have a CAP project and run the `npm install cap2ui5` @@ -20,12 +28,32 @@ line in it. ```bash npm i -g @sap/cds-dk +cds version +``` + +`cds version` must list `@sap/cds-dk (global)` with a `10.x` version. If the +shell answers that `cds` is not found, open a new terminal — see +[Troubleshooting](./troubleshooting#the-cds-command-is-not-found) if that does +not help. Then create the project and add the plugin: + +```bash cds init my-cap2ui5-app --nodejs --add tiny-sample cd my-cap2ui5-app npm install npm install cap2ui5 ``` +`--add tiny-sample` gives the project something to read later: a service +`CatalogService` with one entity `Books` in `srv/cat-service.cds`, and five +books in `db/data/CatalogService.Books.csv`. + +::: details Optional: check the CAP project before adding the plugin +Run `cds watch` after the first `npm install` and before `npm install cap2ui5`, +and open . CAP's index page lists the service endpoint +`/odata/v4/catalog` with `Books`; the link answers the five books as JSON. +That is a plain CAP project working. Stop the server with `Ctrl+C` and go on. +::: + Two things about `cds init` that cost time when missed. Without `--nodejs`, `cds init` (cds-dk 10) writes no `package.json` at all, and there is nothing to install the plugin into. And it fails in a folder whose name contains a @@ -45,7 +73,15 @@ into your repository, and there are no frontend files to serve. ## 2. Your first app -One file in `srv/apps/` — the directory the plugin scans: +One file in `srv/apps/` — the directory the plugin scans. It does not exist +yet in a new project: + +::: details Creating `srv/apps/hello.js` +- **VS Code:** right-click the `srv` folder → **New File…** → type + `apps/hello.js`. The slash creates the `apps` folder along with the file. +- **Terminal:** `mkdir srv/apps` (macOS, Linux, PowerShell) or `mkdir srv\apps` + (Windows cmd.exe), then create `hello.js` in it with your editor. +::: ```js // srv/apps/hello.js @@ -122,6 +158,21 @@ Open that address. Those lines are the entry point: CAP's index page at `http://localhost:4004/` lists the HTML files under `app/` and your CDS services, not this route. +::: details Tip: a link to the app on CAP's index page +The index page lists every `index.html` under `app/`, so a one-line redirect +puts the app there. Create `app/hello/index.html`: + +```html + + +``` + +Then **stop `cds watch` and start it again.** A new folder under `app/` does +not restart the server, and the index page is built once per start: until the +restart, `/hello/` answers 404 and "Web Applications" still says "none". After +it, `/hello` is listed there and opens the app. +::: + The address is the **roundtrip route**, not a static page: the framework answers a GET on it with a page that embeds the whole UI5 component — every module, view and stylesheet — and starts the app named in `app_start`. @@ -151,32 +202,101 @@ A **stateful UI5 app** in one file that ## Reading your own data -The point of running inside CAP. `main` may be `async` when the app does I/O, -and `cds.ql` works exactly as it does in a handler: +The point of running inside CAP: an app reads your entities with `cds.ql`, +exactly as a handler does. This is a complete app for the project from +step 1 — save it as `srv/apps/books.js`, next to `hello.js`: ```js +// srv/apps/books.js import cds from "@sap/cds"; import { defineApp, t } from "cap2ui5"; + const { SELECT } = cds.ql; -defineApp("ZCL_BOOKS", class { +defineApp("BOOKS", class { search = ""; - books = t.table({ ID: 0, title: "", price: t.packed(9, 2) }); + books = t.table({ ID: 0, title: "", author: "" }); async main(c) { - if (c.isDisplay) { c.view(/* … a Table bound to c.bind("books") … */); return; } - + if (c.isFirstRun) { + this.books = await SELECT.from("CatalogService.Books"); + } + if (c.isDisplay) { + c.view(` + + + + + + + + + + + + + + + + + +
+
+
+
`); + return; + } if (c.eventName === "SEARCH") { - const { Books } = cds.entities("my.bookshop"); - this.books = await SELECT.from(Books).where`title like ${"%" + this.search + "%"}`; + this.books = await SELECT.from("CatalogService.Books") + .where`title like ${"%" + this.search + "%"}`; + c.messageToast(`${this.books.length} found`); } } }); ``` -`t.table({…})` declares the row type; `t.packed(9, 2)` a decimal. The whole -tree — structures, tables, nested ones — goes through the draft and comes back -as plain values. +`cds watch` restarts by itself when you save, and the startup lines gain one: + +``` +[cap2ui5] BOOKS http://localhost:4004/sap/bc/z2ui5?app_start=BOOKS +``` + +Open it: five books. Search for `Raven` and one row is left, with a toast +"1 found". Three things the example shows: + +- **`main` is `async`** because the app does I/O — and `isFirstRun` seeds the + table once, before the first render. +- **`t.table({…})` describes one row**, not the table: the field starts empty, + and the object only fixes the columns and their types. +- **Column names are UPPERCASE in the view** — `{TITLE}`, not `{title}`. The + model carries field names uppercase; see [Data Binding](./data-binding#tables). + +## What is in the database + +`cds watch` runs on an **in-memory SQLite** and deploys every table at each +start — the log says `connect to db > sqlite { url: ':memory:' }`. The tables +come from every `.cds` file CAP loads, your `srv/cat-service.cds` and the +plugin's model, which brings `cap2ui5.Drafts`. The books are loaded from the +CSV file; the drafts are written by the plugin, one row per roundtrip. + +That is also why a restart forgets everything. How to list the tables, look +inside them while the app runs, and keep the data across restarts with a +SQLite file is in +[Persistence: the database in development](./persistence#the-database-in-development). + +## When something goes wrong + +| You see | It is | +|---|---| +| `require is not defined in ES module scope` | an app file uses `require` — write `import`, or name the file `.cjs` | +| `NO_DRAFT_ENTRY_OF_PREVIOUS_REQUEST_FOUND` after a code change | the restart emptied the database — reload the tab | +| the browser asks for a login | log in as `alice` and leave the password empty | +| `port 4004 is already in use` | another `cds watch` still runs — stop it, or start with `cds watch --port 4005` | +| a white page | UI5 did not load from the SAP CDN (`sdk.openui5.org`) — check internet or proxy, and open the browser console with `F12` | +| `cds` is not found | open a new terminal; on Windows see [Troubleshooting](./troubleshooting#the-cds-command-is-not-found) | +| `EACCES` on `npm i -g` (macOS, Linux) | install Node with nvm instead of using `sudo` | + +The details for each are in [Troubleshooting](./troubleshooting). ## Next steps @@ -185,3 +305,4 @@ as plain values. - [**Data Binding**](./data-binding) — `c.bind`, tables, structures - [**Persistence**](./persistence) — `cap2ui5.Drafts` and the owner binding - [**Configuration**](../reference/configuration) — routes, the auth default, the apps directory +- [**Deployment**](../reference/deployment) — to BTP: `cap2ui5.Drafts` becomes an HDI table in the HANA build, and the approuter needs one extra route diff --git a/docs/guide/persistence.md b/docs/guide/persistence.md index cbda1d3..8204cf1 100644 --- a/docs/guide/persistence.md +++ b/docs/guide/persistence.md @@ -99,6 +99,93 @@ open over lunch is fine; one left open overnight starts fresh. Both the sweep and the framework's willingness to resume follow the same number, and your [user exit](./user-exit#onroundtrip) sets it. +## The database in development + +Everything here was measured with `cds watch` on CAP 10.1 in a project from +`cds init --nodejs --add tiny-sample`, as the [Quickstart](./getting-started) +creates it. + +### Where the tables come from + +`cds watch` connects to an **in-memory SQLite** and deploys every table at each +start: + +``` +[cds] - loaded model from 2 file(s): + srv/cat-service.cds + node_modules/cap2ui5/index.cds +[cds] - connect to db > sqlite { url: ':memory:' } +``` + +The tables come from every `.cds` file CAP loads — a `db/` folder with a +schema is not required. `CatalogService.Books` is defined in +`srv/cat-service.cds`; `cap2ui5.Drafts` comes from the plugin's model. + +### Where the rows come from + +- **Your data** comes from CSV files in `db/data/`, matched to an entity by + file name: `db/data/CatalogService.Books.csv` fills `CatalogService.Books`, + and the log says `> init from db/data/CatalogService.Books.csv`. +- **Drafts** are written by the plugin: one row per roundtrip — the app start + and every click — with the owner (`alice`) and the serialized app instance. + The `data` column holds your fields as XML, `1` for the + quickstart's counter. Rows older than four hours are deleted on each + roundtrip ([Retention](#retention)). + +### See the tables + +```bash +cds compile "*" --to sql +``` + +prints the `CREATE TABLE` statements: `CatalogService_Books`, +`cap2ui5_Drafts`, and `cds_outbox_Messages`, which is CAP's own. The double +quotes work in bash, PowerShell and cmd.exe alike. + +### Look inside while the app runs + +```bash +cds repl --run . +``` + +starts the server in a REPL, on a **random port** — take the address from the +`[cap2ui5]` lines it prints. Use the app, then query: + +```js +await SELECT.from("cap2ui5.Drafts").columns("id","owner","createdAt") +``` + +After the start of an app and one click, that answers two rows, both owned by +`alice`. `.exit` leaves the REPL and stops the server. + +### Keep the data across restarts + +Point the database at a file in `package.json`: + +```json +{ + "cds": { + "requires": { + "db": { "kind": "sqlite", "credentials": { "url": "db.sqlite" } } + } + } +} +``` + +and run `cds deploy` once, which creates `db.sqlite` with the tables and the +CSV data. From then on `cds watch` says `connect to db > sqlite { url: +'db.sqlite' }`, and drafts and data survive a restart — a tab that was open +before the restart keeps working, where the in-memory database answers +`NO_DRAFT_ENTRY_OF_PREVIOUS_REQUEST_FOUND`. + +- **Running `cds deploy` again rebuilds the tables** and deletes everything the + apps wrote, drafts included. Open tabs then need a reload. +- **`cds init` already lists `*.sqlite` in `.gitignore`**, so the file stays + out of the repository. +- **Give SQLite a busy timeout** as soon as a second process writes to the + same file — the SQLite warning in + [Database Model](../reference/database#retention) has the setting. + ## Next - [**Database Model**](../reference/database) — the entity and the owner binding diff --git a/docs/guide/troubleshooting.md b/docs/guide/troubleshooting.md index f0373d1..6cf31d0 100644 --- a/docs/guide/troubleshooting.md +++ b/docs/guide/troubleshooting.md @@ -4,6 +4,52 @@ Before anything else: press **`Ctrl+F12`**. The [developer tools](./devtools) answer most of what follows in one look — which app is serving, what the last roundtrip sent and received, and whether something threw. +## Setup problems + +### The `cds` command is not found + +`npm i -g @sap/cds-dk` succeeded, but the shell answers that `cds` is not +recognized. A terminal that was open during the install does not see the new +command yet — open a new one. `cds version` must then list +`@sap/cds-dk (global)` with a `10.x` version. + +On Windows PowerShell the error can instead say that *running scripts is +disabled on this system*: PowerShell refuses the `cds.ps1` shim npm installed. +Allow locally installed scripts for your user once: + +```powershell +Set-ExecutionPolicy -Scope CurrentUser RemoteSigned +``` + +### `EACCES` on `npm i -g` + +On macOS and Linux, a Node.js installed system-wide keeps its global packages +in a directory your user cannot write. Do not work around it with `sudo`: +install Node.js with [nvm](https://github.com/nvm-sh/nvm), which keeps +everything in your home directory, and run `npm i -g @sap/cds-dk` again. + +### `port 4004 is already in use` + +``` +[EADDRINUSE] - port 4004 is already in use by another server process. +``` + +Another `cds watch` still runs, usually in a different terminal. Stop it with +`Ctrl+C` there, or start this one on another port with +`cds watch --port 4005` — the `[cap2ui5]` lines then print the new address. + +### The browser asks for a login + +That is expected: the route requires a user, and `cds watch` uses CAP's +mocked authentication. Log in as **`alice` with an empty password** — the +startup line names it: `[cap2ui5] development login: alice (empty password)`. +Only cancelling the dialog is refused, with a `401`, and the next attempt asks +again. + +Mocked authentication lets other names in as well, but a draft belongs to the +user who created it — log in as someone else and a running session starts +over. The details are under *401 on the roundtrip* below. + ## "App with name X not found" Apps are registered by **`defineApp`**, not found by file name: @@ -51,6 +97,11 @@ the file to `.cjs`, where `require` stays valid. Symptom: `NO_DRAFT_ENTRY_OF_PREVIOUS_REQUEST_FOUND`, or the app restarts from scratch on every interaction. +- **The server restarted.** The most common case in development: you saved a + file, `cds watch` restarted with a fresh in-memory database, and the tab + still holds the id of a draft that no longer exists. Reload the tab. To keep + drafts across restarts, use a SQLite file — see + [Persistence](./persistence#keep-the-data-across-restarts). - **A different user is asking.** Draft rows are bound to their owner and are not readable by anyone else — by design (see [Database](../reference/database)). A draft id from someone else's session, or from before you logged in as