From b4026c3e661498e66ad1a50af2310dc679303bb4 Mon Sep 17 00:00:00 2001 From: "Sam (automated drift fix)" Date: Thu, 22 Oct 2026 11:21:10 +0300 Subject: [PATCH] docs(entities): document concurrency behavior of bulk methods Add notes to updateMany(), bulkCreate(), bulkUpdate(), deleteMany(), and upsert() saying they aren't transactions, and explain how to claim a single record safely with updateMany(). Each note links to the new Concurrent updates section in Entity Schemas. Co-Authored-By: Claude Opus 5.5 --- packages/sdk/src/modules/entities.types.ts | 41 ++++++++++++++++++++++ 1 file changed, 41 insertions(+) diff --git a/packages/sdk/src/modules/entities.types.ts b/packages/sdk/src/modules/entities.types.ts index 5faff31a..d41a2834 100644 --- a/packages/sdk/src/modules/entities.types.ts +++ b/packages/sdk/src/modules/entities.types.ts @@ -773,6 +773,10 @@ export interface EntityHandler { * * Permanently removes all records that match the provided query. * + * This isn't a transaction. Each record is deleted on its own, so if the call + * fails partway, the records already deleted stay deleted. Learn more about + * [concurrent updates](/developers/backend/resources/entities/entity-schemas#concurrent-updates). + * * @param query - Query matching {@linkcode EntityFilterQuery}. Every matching record is deleted. * @returns Promise resolving to the deletion result. * @@ -794,6 +798,10 @@ export interface EntityHandler { * Efficiently creates multiple records at once. This is faster * than creating them individually. * + * This isn't a transaction. Each record is written on its own, so if the call + * fails partway, the records already written stay written. Learn more about + * [concurrent updates](/developers/backend/resources/entities/entity-schemas#concurrent-updates). + * * @param data - Array of record data objects. * @returns Promise resolving to an array of created records. * @@ -822,6 +830,15 @@ export interface EntityHandler { * so you don't re-process the same entities on each iteration. For * example, filter by `status: 'pending'` when setting status to `'processed'`. * + * Each matching record is updated on its own, and the call isn't a transaction. + * To claim one record safely, so that only one of 2 users acting at the same + * time succeeds, pass that record's `id` as a plain value plus the conditions + * that must still be true, then check `updated` in the response. The conditions + * are only checked again at write time when the query names a single `id`. Any + * other query, including several IDs in `$in` or `$or`, finds the matching + * records first and then updates them. Learn more about + * [concurrent updates](/developers/backend/resources/entities/entity-schemas#concurrent-updates). + * * To update a single record by ID, use {@linkcode update | update()} instead. To update * multiple specific records with different data each, use {@linkcode bulkUpdate | bulkUpdate()}. * @@ -881,6 +898,21 @@ export interface EntityHandler { * hasMore = result.has_more; * } * ``` + * + * @example + * ```typescript + * // Claim a record + * // Only one user can claim the task, even if several try at the same time + * const result = await base44.entities.Task.updateMany( + * { id: taskId, status: 'open' }, + * { $set: { status: 'claimed', claimed_by: user.email } } + * ); + * if (result.updated === 1) { + * // This user claimed the task + * } else { + * // Someone else claimed it first, or it was no longer open + * } + * ``` */ updateMany( query: Partial, @@ -998,6 +1030,11 @@ export interface EntityHandler { * key, the last one wins. Updates merge the given fields into the existing record, like * {@linkcode update | update()}. * + * This isn't a transaction. Each record is written on its own, so if the call + * fails partway, the records already written stay written. Concurrent calls with + * the same key can also create duplicate records. Learn more about + * [concurrent updates](/developers/backend/resources/entities/entity-schemas#concurrent-updates). + * * @param records - Array of record data objects. Each must carry a value, not an object or * array, for every field named in `options.key`, to find or create by. Other fields are * optional, and only the ones you include are merged into a matched record, like `update()`. @@ -1038,6 +1075,10 @@ export interface EntityHandler { * * You can update up to 500 records per request. * + * This isn't a transaction. Each record is written on its own, so if the call + * fails partway, the records already written stay written. Learn more about + * [concurrent updates](/developers/backend/resources/entities/entity-schemas#concurrent-updates). + * * To apply the same update to all records matching a query, use * {@linkcode updateMany | updateMany()}. To update a single record by ID, use * {@linkcode update | update()}.