From 1fc873cbd6acf0819a72a0e6919e6b9ca2cfe65a Mon Sep 17 00:00:00 2001 From: Sebastian Van Syckel Date: Mon, 13 Jul 2026 10:06:04 +0200 Subject: [PATCH 1/7] fix: lazy array if no returning clause --- node.js/app-services.md | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/node.js/app-services.md b/node.js/app-services.md index bcfdce896..b301e18ad 100644 --- a/node.js/app-services.md +++ b/node.js/app-services.md @@ -164,17 +164,17 @@ When CAP's generic handlers run a CRUD operation, the result follows a consisten | 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 | +| `CREATE` | Array with `.affected` (rows written); populated with rows from a `RETURNING` clause, else lazily materializes the created 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 | -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. +For `CREATE`, the array is populated with rows from a SQL `RETURNING` clause. As `RETURNING` is not yet supported, the result falls back to a lazy array: iterating it (`[...result]`, `for…of`, `JSON.stringify`) materializes the generated primary keys of the created rows. Direct index access works after the first iteration. ```js -const inserted = await srv.create(Books).entries({title:'Catweazle'}) -inserted.affected // 1 -const [row] = [...inserted] // materializes — row holds the generated key -inserted[0] // same row (materialized above) +const created = await srv.create(Books).entries({title:'Catweazle'}) +created.affected // 1 +const [row] = [...created] // materializes — row holds the generated key +created[0] // same row (materialized above) ``` 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: From 0706b0d45288b508312204aaacd305109f9868cb Mon Sep 17 00:00:00 2001 From: Sebastian Van Syckel Date: Mon, 13 Jul 2026 10:11:38 +0200 Subject: [PATCH 2/7] improve --- node.js/app-services.md | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/node.js/app-services.md b/node.js/app-services.md index b301e18ad..81843d40d 100644 --- a/node.js/app-services.md +++ b/node.js/app-services.md @@ -164,20 +164,23 @@ When CAP's generic handlers run a CRUD operation, the result follows a consisten | Operation | Return value | |-----------------------|-----------------------------------------------------------------------------------------------| | `READ` | Array of matching records, or a single record / `null` when read by key | -| `CREATE` | Array with `.affected` (rows written); populated with rows from a `RETURNING` clause, else lazily materializes the created 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 | +| `CREATE` | Array with `.affected` (rows written); populated with rows from a `RETURNING` clause | +| `UPDATE` / `UPSERT` | Array with `.affected` (rows changed); reserved for rows from a `RETURNING` clause | +| `DELETE` | Array with `.affected` (rows deleted); reserved for rows from a `RETURNING` clause | -For `CREATE`, the array is populated with rows from a SQL `RETURNING` clause. As `RETURNING` is not yet supported, the result falls back to a lazy array: iterating it (`[...result]`, `for…of`, `JSON.stringify`) materializes the generated primary keys of the created rows. Direct index access works after the first iteration. +For `CREATE`, the array is populated with rows from a SQL `RETURNING` clause. As `RETURNING` is not yet supported, the result falls back to a lazy array that computes the created rows' generated primary keys on demand: iterating it (`[...result]`, `for…of`, `JSON.stringify`) populates those keys, avoiding the cost when you don't need them. + +> [!warning] Iterate before indexing +> Direct index access (`result[0]`) returns `undefined` until the array has been iterated at least once. Spread or loop over the result first. ```js const created = await srv.create(Books).entries({title:'Catweazle'}) created.affected // 1 -const [row] = [...created] // materializes — row holds the generated key -created[0] // same row (materialized above) +const [row] = [...created] // iterate first — row holds the generated key +created[0] // same row (populated by the iteration above) ``` -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: +For `UPDATE`, `UPSERT`, and `DELETE`, the array is reserved for rows returned by a SQL `RETURNING` clause. Unlike `CREATE`, there are no generated keys to synthesize client-side, so — with `RETURNING` not yet supported — the array is currently always empty: ```js const updated = await srv.update(Books).set({discount:'10%'}).where({stock:{'>':111}}) From 72236577ea7b325d4e960612baf2f39cb8259ff6 Mon Sep 17 00:00:00 2001 From: Sebastian Van Syckel Date: Mon, 13 Jul 2026 10:15:10 +0200 Subject: [PATCH 3/7] better --- node.js/app-services.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/node.js/app-services.md b/node.js/app-services.md index 81843d40d..c2c819e1d 100644 --- a/node.js/app-services.md +++ b/node.js/app-services.md @@ -164,11 +164,11 @@ When CAP's generic handlers run a CRUD operation, the result follows a consisten | Operation | Return value | |-----------------------|-----------------------------------------------------------------------------------------------| | `READ` | Array of matching records, or a single record / `null` when read by key | -| `CREATE` | Array with `.affected` (rows written); populated with rows from a `RETURNING` clause | +| `CREATE` | Array with `.affected` (rows written); iterate to access the created rows' generated keys | | `UPDATE` / `UPSERT` | Array with `.affected` (rows changed); reserved for rows from a `RETURNING` clause | | `DELETE` | Array with `.affected` (rows deleted); reserved for rows from a `RETURNING` clause | -For `CREATE`, the array is populated with rows from a SQL `RETURNING` clause. As `RETURNING` is not yet supported, the result falls back to a lazy array that computes the created rows' generated primary keys on demand: iterating it (`[...result]`, `for…of`, `JSON.stringify`) populates those keys, avoiding the cost when you don't need them. +For `CREATE`, the array will be populated with rows from a SQL `RETURNING` clause once that is supported. Until then, the result is a lazy array that computes the created rows' generated primary keys on demand: iterating it (`[...result]`, `for…of`, `JSON.stringify`) populates those keys, avoiding the cost when you don't need them. > [!warning] Iterate before indexing > Direct index access (`result[0]`) returns `undefined` until the array has been iterated at least once. Spread or loop over the result first. From 2d4d656dacd08307aa07297c5a2f6ee48aa6aeaf Mon Sep 17 00:00:00 2001 From: Sebastian Van Syckel Date: Mon, 13 Jul 2026 10:16:41 +0200 Subject: [PATCH 4/7] UPDATE vs UPSERT --- node.js/app-services.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/node.js/app-services.md b/node.js/app-services.md index c2c819e1d..1548b5b3c 100644 --- a/node.js/app-services.md +++ b/node.js/app-services.md @@ -165,7 +165,8 @@ When CAP's generic handlers run a CRUD operation, the result follows a consisten |-----------------------|-----------------------------------------------------------------------------------------------| | `READ` | Array of matching records, or a single record / `null` when read by key | | `CREATE` | Array with `.affected` (rows written); iterate to access the created rows' generated keys | -| `UPDATE` / `UPSERT` | Array with `.affected` (rows changed); reserved for rows from a `RETURNING` clause | +| `UPDATE` | Array with `.affected` (rows changed); reserved for rows from a `RETURNING` clause | +| `UPSERT` | Array with `.affected` (rows written); reserved for rows from a `RETURNING` clause | | `DELETE` | Array with `.affected` (rows deleted); reserved for rows from a `RETURNING` clause | For `CREATE`, the array will be populated with rows from a SQL `RETURNING` clause once that is supported. Until then, the result is a lazy array that computes the created rows' generated primary keys on demand: iterating it (`[...result]`, `for…of`, `JSON.stringify`) populates those keys, avoiding the cost when you don't need them. From 73ec45d5704157dcf60d413d8aeee00916df0c3f Mon Sep 17 00:00:00 2001 From: Sebastian Van Syckel Date: Mon, 13 Jul 2026 10:19:54 +0200 Subject: [PATCH 5/7] rows created --- 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 1548b5b3c..703477575 100644 --- a/node.js/app-services.md +++ b/node.js/app-services.md @@ -164,7 +164,7 @@ When CAP's generic handlers run a CRUD operation, the result follows a consisten | Operation | Return value | |-----------------------|-----------------------------------------------------------------------------------------------| | `READ` | Array of matching records, or a single record / `null` when read by key | -| `CREATE` | Array with `.affected` (rows written); iterate to access the created rows' generated keys | +| `CREATE` | Array with `.affected` (rows created); iterate to access the created rows' generated keys | | `UPDATE` | Array with `.affected` (rows changed); reserved for rows from a `RETURNING` clause | | `UPSERT` | Array with `.affected` (rows written); reserved for rows from a `RETURNING` clause | | `DELETE` | Array with `.affected` (rows deleted); reserved for rows from a `RETURNING` clause | From b5ba7d217d28dc542e7c88b9d3bc8b49b41fb0d7 Mon Sep 17 00:00:00 2001 From: sjvans <30337871+sjvans@users.noreply.github.com> Date: Mon, 13 Jul 2026 15:08:12 +0200 Subject: [PATCH 6/7] Update node.js/app-services.md --- node.js/app-services.md | 1 - 1 file changed, 1 deletion(-) diff --git a/node.js/app-services.md b/node.js/app-services.md index 703477575..a4692843d 100644 --- a/node.js/app-services.md +++ b/node.js/app-services.md @@ -178,7 +178,6 @@ For `CREATE`, the array will be populated with rows from a SQL `RETURNING` claus const created = await srv.create(Books).entries({title:'Catweazle'}) created.affected // 1 const [row] = [...created] // iterate first — row holds the generated key -created[0] // same row (populated by the iteration above) ``` For `UPDATE`, `UPSERT`, and `DELETE`, the array is reserved for rows returned by a SQL `RETURNING` clause. Unlike `CREATE`, there are no generated keys to synthesize client-side, so — with `RETURNING` not yet supported — the array is currently always empty: From 46d33c6614976f0b7b74171b11eccfc0f5bff445 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9=20Jeglinsky?= Date: Mon, 13 Jul 2026 15:20:09 +0200 Subject: [PATCH 7/7] Apply suggestions from code review MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: René Jeglinsky --- node.js/app-services.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/node.js/app-services.md b/node.js/app-services.md index a4692843d..8fa0e4980 100644 --- a/node.js/app-services.md +++ b/node.js/app-services.md @@ -169,7 +169,7 @@ When CAP's generic handlers run a CRUD operation, the result follows a consisten | `UPSERT` | Array with `.affected` (rows written); reserved for rows from a `RETURNING` clause | | `DELETE` | Array with `.affected` (rows deleted); reserved for rows from a `RETURNING` clause | -For `CREATE`, the array will be populated with rows from a SQL `RETURNING` clause once that is supported. Until then, the result is a lazy array that computes the created rows' generated primary keys on demand: iterating it (`[...result]`, `for…of`, `JSON.stringify`) populates those keys, avoiding the cost when you don't need them. +For `CREATE`, the array will be populated with rows from an SQL `RETURNING` clause once that is supported. Until then, the result is a lazy array that computes the created rows' generated primary keys on demand: iterating it (`[...result]`, `for…of`, `JSON.stringify`) populates those keys, avoiding the cost when you don't need them. > [!warning] Iterate before indexing > Direct index access (`result[0]`) returns `undefined` until the array has been iterated at least once. Spread or loop over the result first. @@ -180,7 +180,7 @@ created.affected // 1 const [row] = [...created] // iterate first — row holds the generated key ``` -For `UPDATE`, `UPSERT`, and `DELETE`, the array is reserved for rows returned by a SQL `RETURNING` clause. Unlike `CREATE`, there are no generated keys to synthesize client-side, so — with `RETURNING` not yet supported — the array is currently always empty: +For `UPDATE`, `UPSERT`, and `DELETE`, the array is reserved for rows returned by an SQL `RETURNING` clause. Unlike `CREATE`, there are no generated keys to synthesize client-side, so — with `RETURNING` not yet supported — the array is currently always empty: ```js const updated = await srv.update(Books).set({discount:'10%'}).where({stock:{'>':111}})