From f23a85788c2e68f4cdf213e2b308a25c9f72833f Mon Sep 17 00:00:00 2001 From: D050513 Date: Mon, 29 Jun 2026 12:27:33 +0200 Subject: [PATCH 1/5] Node.js: Clarify Consistent Return Values --- node.js/app-services.md | 30 ++++++++++++++++-------------- 1 file changed, 16 insertions(+), 14 deletions(-) diff --git a/node.js/app-services.md b/node.js/app-services.md index eb831f098..70a1bce8e 100644 --- a/node.js/app-services.md +++ b/node.js/app-services.md @@ -159,27 +159,29 @@ cds.ApplicationService.handle_log_events = cds.service.impl (function(){ ## Results of Generic CRUD Handlers -Custom `.on` handlers can return any value. When no custom handler provides a result — that is, when CAP's built-in generic handler runs the CRUD operation — the result follows a consistent shape: +When CAP's generic handlers run a CRUD operation, the result follows a consistent shape (custom `.on` handlers may return any value): -| Operation | Return value | -|-----------|-------------| -| **READ** | `object[]` — the matching records, or a single `object \| null` for singleton requests | -| **INSERT** / **CREATE** | An array of primary-key objects of the inserted rows, with `.affected` set to the number of rows written | -| **UPDATE** / **UPSERT** / **DELETE** | An empty array with `.affected` set to the number of rows changed or deleted | +| Operation | Return value | +|-----------------------|-------------------------------------------------------------------------------------------| +| `READ` | Array of matching records, or a single record / `null` when read by key | +| `INSERT` / `CREATE` | Array with `.affected` (rows written), populated with rows from a `RETURNING` clause | +| `UPDATE` / `UPSERT` | Array with `.affected` (rows changed), populated with rows from a `RETURNING` clause | +| `DELETE` | Array with `.affected` (rows deleted), populated with rows from a `RETURNING` clause | -The `.affected` count is a property directly on the returned array: +SQL `RETURNING` is not yet supported, so the array is currently always empty. For `INSERT`s, you can spread the result to access the generated primary keys — a transitional convenience until `RETURNING` lands. Once an `INSERT` query includes a `RETURNING` clause, the array is populated directly with the returned rows. ```js const inserted = await srv.create(Books).entries({title:'Catweazle'}) -inserted[0] // { ID: '...' } — primary key of the inserted row -inserted.affected // 1 +inserted.affected // 1 +const [row] = [...inserted] // generated primary keys const updated = await srv.update(Books).set({discount:'10%'}).where({stock:{'>':111}}) -updated.affected // number of rows updated +updated.affected // number of rows updated ``` -When a write targets a **specific subject** (for example, `srv.update(Books, 201)` or `srv.delete(Books, '1')`) and no row is matched, the handler throws a `404` error. A query using only a `where` clause with zero matches returns `{ affected: 0 }` without throwing. +When a write targets a single row by key (for example, `srv.update(Books, 201)` or `srv.delete(Books, '1')`) and no row matches, the handler throws a 404 error. A `where` clause that matches zero rows returns an array with `affected: 0` without throwing. -::: tip Consistent results across local and remote services -This return shape was introduced in **cds 10** to make results from local services, HCQL-proxied remote services, and database services consistent. To restore the previous behavior, set `{ "features": { "legacy_srv_results": true } }` in your project configuration. -::: +> [!tip] Consistent Results Across Local and Remote Services +> This shape was introduced in cds 10 so that local services, HCQL-proxied remote services, and database services return the same thing. To restore the previous behavior, set cds.features.legacy_srv_results: true. + +[See the migration guide for opt-out options.](../migration/cds10#fixed-service-results){.learn-more} From b78718a38373dbc2a6feb1b5849b58ea6e4f4c19 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9=20Jeglinsky?= Date: Mon, 29 Jun 2026 12:54:37 +0200 Subject: [PATCH 2/5] Apply suggestion from @renejeglinsky --- node.js/app-services.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/node.js/app-services.md b/node.js/app-services.md index 70a1bce8e..58d9a8ebb 100644 --- a/node.js/app-services.md +++ b/node.js/app-services.md @@ -184,4 +184,4 @@ When a write targets a single row by key (for example, `srv.update(Books, 201)` > [!tip] Consistent Results Across Local and Remote Services > This shape was introduced in cds 10 so that local services, HCQL-proxied remote services, and database services return the same thing. To restore the previous behavior, set cds.features.legacy_srv_results: true. -[See the migration guide for opt-out options.](../migration/cds10#fixed-service-results){.learn-more} +[See the migration guide for opt-out options.](../releases/migration/cds10#fixed-service-results){.learn-more} From 7f53633f1c595497d9eabb0e4ba54a09b350b6e2 Mon Sep 17 00:00:00 2001 From: D050513 Date: Mon, 29 Jun 2026 13:24:14 +0200 Subject: [PATCH 3/5] better --- node.js/app-services.md | 21 +++++++++++++-------- 1 file changed, 13 insertions(+), 8 deletions(-) diff --git a/node.js/app-services.md b/node.js/app-services.md index 58d9a8ebb..80268046d 100644 --- a/node.js/app-services.md +++ b/node.js/app-services.md @@ -161,20 +161,25 @@ cds.ApplicationService.handle_log_events = cds.service.impl (function(){ When CAP's generic handlers run a CRUD operation, the result follows a consistent shape (custom `.on` handlers may return any value): -| Operation | Return value | -|-----------------------|-------------------------------------------------------------------------------------------| -| `READ` | Array of matching records, or a single record / `null` when read by key | -| `INSERT` / `CREATE` | Array with `.affected` (rows written), populated with rows from a `RETURNING` clause | -| `UPDATE` / `UPSERT` | Array with `.affected` (rows changed), populated with rows from a `RETURNING` clause | -| `DELETE` | Array with `.affected` (rows deleted), populated with rows from a `RETURNING` clause | +| Operation | Return value | +|-----------------------|-----------------------------------------------------------------------------------------------| +| `READ` | Array of matching records, or a single record / `null` when read by key | +| `INSERT` / `CREATE` | Array with `.affected` (rows written); iterate to access the inserted rows' primary keys | +| `UPDATE` / `UPSERT` | Array with `.affected` (rows changed); populated with rows from a `RETURNING` clause | +| `DELETE` | Array with `.affected` (rows deleted); populated with rows from a `RETURNING` clause | -SQL `RETURNING` is not yet supported, so the array is currently always empty. For `INSERT`s, you can spread the result to access the generated primary keys — a transitional convenience until `RETURNING` lands. Once an `INSERT` query includes a `RETURNING` clause, the array is populated directly with the returned rows. +For `INSERT`s, the result is a lazy array: iterating it (`[...result]`, `for…of`, `JSON.stringify`) materializes the inserted rows' generated primary keys in place. Direct index access works after the first iteration. ```js const inserted = await srv.create(Books).entries({title:'Catweazle'}) inserted.affected // 1 -const [row] = [...inserted] // generated primary keys +const [row] = [...inserted] // materializes — row holds the generated key +inserted[0] // same row (materialized above) +``` + +For `UPDATE`, `UPSERT`, and `DELETE`, the array is reserved for rows returned by a SQL `RETURNING` clause — not yet supported, so currently always empty: +```js const updated = await srv.update(Books).set({discount:'10%'}).where({stock:{'>':111}}) updated.affected // number of rows updated ``` From 9cae3f128d4ffa7edf93d2f36de14052040a7620 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9=20Jeglinsky?= Date: Tue, 30 Jun 2026 15:20:55 +0200 Subject: [PATCH 4/5] Update node.js/app-services.md --- node.js/app-services.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/node.js/app-services.md b/node.js/app-services.md index 80268046d..a0b61db34 100644 --- a/node.js/app-services.md +++ b/node.js/app-services.md @@ -177,7 +177,7 @@ const [row] = [...inserted] // materializes — row holds the generated key inserted[0] // same row (materialized above) ``` -For `UPDATE`, `UPSERT`, and `DELETE`, the array is reserved for rows returned by a SQL `RETURNING` clause — not yet supported, so currently always empty: +For `UPDATE`, `UPSERT`, and `DELETE`, the array is reserved for rows returned by a SQL `RETURNING` clause. But `RETURNING` is not yet supported, so the array currently is always empty: ```js const updated = await srv.update(Books).set({discount:'10%'}).where({stock:{'>':111}}) From 30792f3e3eec3a2213866757512c97252ee30f32 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9=20Jeglinsky?= Date: Tue, 30 Jun 2026 15:25:35 +0200 Subject: [PATCH 5/5] Apply suggestion from @renejeglinsky --- node.js/app-services.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/node.js/app-services.md b/node.js/app-services.md index a0b61db34..bcfdce896 100644 --- a/node.js/app-services.md +++ b/node.js/app-services.md @@ -168,7 +168,7 @@ When CAP's generic handlers run a CRUD operation, the result follows a consisten | `UPDATE` / `UPSERT` | Array with `.affected` (rows changed); populated with rows from a `RETURNING` clause | | `DELETE` | Array with `.affected` (rows deleted); populated with rows from a `RETURNING` clause | -For `INSERT`s, the result is a lazy array: iterating it (`[...result]`, `for…of`, `JSON.stringify`) materializes the inserted rows' generated primary keys in place. Direct index access works after the first iteration. +For `INSERT`s, the result is a lazy array: iterating it (`[...result]`, `for…of`, `JSON.stringify`) materializes the generated primary keys of the inserted rows. Direct index access works after the first iteration. ```js const inserted = await srv.create(Books).entries({title:'Catweazle'})